root / trunk / twisted / spread / banana.py

Revision 24441, 10.5 kB (checked in by thijs, 1 year ago)

Merge maintainer-email-2438: Get rid of references to maintainer email addresses from code.

Author: thijs
Reviewer: exarkun
Fixes: #2438

Line 
1 # -*- test-case-name: twisted.test.test_banana -*-
2 #
3 # Copyright (c) 2001-2004 Twisted Matrix Laboratories.
4 # See LICENSE for details.
5
6
7 """Banana -- s-exp based protocol.
8
9 Future Plans: This module is almost entirely stable.  The same caveat applies
10 to it as applies to L{twisted.spread.jelly}, however.  Read its future plans
11 for more details.
12
13 @author: Glyph Lefkowitz
14 """
15
16 __version__ = "$Revision: 1.37 $"[11:-2]
17
18 from twisted.internet import protocol
19 from twisted.persisted import styles
20 from twisted.python import log
21
22 import copy, cStringIO, struct
23
24 class BananaError(Exception):
25     pass
26
27 def int2b128(integer, stream):
28     if integer == 0:
29         stream(chr(0))
30         return
31     assert integer > 0, "can only encode positive integers"
32     while integer:
33         stream(chr(integer & 0x7f))
34         integer = integer >> 7
35
36 def b1282int(st):
37     oneHundredAndTwentyEight = 128l
38     i = 0
39     place = 0
40     for char in st:
41         num = ord(char)
42         i = i + (num * (oneHundredAndTwentyEight ** place))
43         place = place + 1
44     if i <= 2147483647:
45         return int(i)
46     else:
47         return i
48
49 # delimiter characters.
50 LIST     = chr(0x80)
51 INT      = chr(0x81)
52 STRING   = chr(0x82)
53 NEG      = chr(0x83)
54 FLOAT    = chr(0x84)
55 # "optional" -- these might be refused by a low-level implementation.
56 LONGINT  = chr(0x85)
57 LONGNEG  = chr(0x86)
58 # really optional; this is is part of the 'pb' vocabulary
59 VOCAB    = chr(0x87)
60
61 HIGH_BIT_SET = chr(0x80)
62
63 def setPrefixLimit(limit):
64     """
65     Set the limit on the prefix length for all Banana connections
66     established after this call.
67
68     The prefix length limit determines how many bytes of prefix a banana
69     decoder will allow before rejecting a potential object as too large.
70
71     @type limit: C{int}
72     @param limit: The number of bytes of prefix for banana to allow when
73     decoding.
74     """
75     global _PREFIX_LIMIT
76     _PREFIX_LIMIT = limit
77 setPrefixLimit(64)
78
79 SIZE_LIMIT = 640 * 1024   # 640k is all you'll ever need :-)
80
81 class Banana(protocol.Protocol, styles.Ephemeral):
82     knownDialects = ["pb", "none"]
83
84     prefixLimit = None
85     sizeLimit = SIZE_LIMIT
86
87     def setPrefixLimit(self, limit):
88         """
89         Set the prefix limit for decoding done by this protocol instance.
90
91         @see: L{setPrefixLimit}
92         """
93         self.prefixLimit = limit
94         self._smallestLongInt = -2 ** (limit * 7) + 1
95         self._smallestInt = -2 ** 31
96         self._largestInt = 2 ** 31 - 1
97         self._largestLongInt = 2 ** (limit * 7) - 1
98
99
100     def connectionReady(self):
101         """Surrogate for connectionMade
102         Called after protocol negotiation.
103         """
104
105     def _selectDialect(self, dialect):
106         self.currentDialect = dialect
107         self.connectionReady()
108
109     def callExpressionReceived(self, obj):
110         if self.currentDialect:
111             self.expressionReceived(obj)
112         else:
113             # this is the first message we've received
114             if self.isClient:
115                 # if I'm a client I have to respond
116                 for serverVer in obj:
117                     if serverVer in self.knownDialects:
118                         self.sendEncoded(serverVer)
119                         self._selectDialect(serverVer)
120                         break
121                 else:
122                     # I can't speak any of those dialects.
123                     log.msg('error losing')
124                     self.transport.loseConnection()
125             else:
126                 if obj in self.knownDialects:
127                     self._selectDialect(obj)
128                 else:
129                     # the client just selected a protocol that I did not suggest.
130                     log.msg('freaky losing')
131                     self.transport.loseConnection()
132
133
134     def connectionMade(self):
135         self.setPrefixLimit(_PREFIX_LIMIT)
136         self.currentDialect = None
137         if not self.isClient:
138             self.sendEncoded(self.knownDialects)
139
140
141     def gotItem(self, item):
142         l = self.listStack
143         if l:
144             l[-1][1].append(item)
145         else:
146             self.callExpressionReceived(item)
147
148     buffer = ''
149
150     def dataReceived(self, chunk):
151         buffer = self.buffer + chunk
152         listStack = self.listStack
153         gotItem = self.gotItem
154         while buffer:
155             assert self.buffer != buffer, "This ain't right: %s %s" % (repr(self.buffer), repr(buffer))
156             self.buffer = buffer
157             pos = 0
158             for ch in buffer:
159                 if ch >= HIGH_BIT_SET:
160                     break
161                 pos = pos + 1
162             else:
163                 if pos > self.prefixLimit:
164                     raise BananaError("Security precaution: more than %d bytes of prefix" % (self.prefixLimit,))
165                 return
166             num = buffer[:pos]
167             typebyte = buffer[pos]
168             rest = buffer[pos+1:]
169             if len(num) > self.prefixLimit:
170                 raise BananaError("Security precaution: longer than %d bytes worth of prefix" % (self.prefixLimit,))
171             if typebyte == LIST:
172                 num = b1282int(num)
173                 if num > SIZE_LIMIT:
174                     raise BananaError("Security precaution: List too long.")
175                 listStack.append((num, []))
176                 buffer = rest
177             elif typebyte == STRING:
178                 num = b1282int(num)
179                 if num > SIZE_LIMIT:
180                     raise BananaError("Security precaution: String too long.")
181                 if len(rest) >= num:
182                     buffer = rest[num:]
183                     gotItem(rest[:num])
184                 else:
185                     return
186             elif typebyte == INT:
187                 buffer = rest
188                 num = b1282int(num)
189                 gotItem(int(num))
190             elif typebyte == LONGINT:
191                 buffer = rest
192                 num = b1282int(num)
193                 gotItem(long(num))
194             elif typebyte == LONGNEG:
195                 buffer = rest
196                 num = b1282int(num)
197                 gotItem(-long(num))
198             elif typebyte == NEG:
199                 buffer = rest
200                 num = -b1282int(num)
201                 gotItem(num)
202             elif typebyte == VOCAB:
203                 buffer = rest
204                 num = b1282int(num)
205                 gotItem(self.incomingVocabulary[num])
206             elif typebyte == FLOAT:
207                 if len(rest) >= 8:
208                     buffer = rest[8:]
209                     gotItem(struct.unpack("!d", rest[:8])[0])
210                 else:
211                     return
212             else:
213                 raise NotImplementedError(("Invalid Type Byte %r" % (typebyte,)))
214             while listStack and (len(listStack[-1][1]) == listStack[-1][0]):
215                 item = listStack.pop()[1]
216                 gotItem(item)
217         self.buffer = ''
218
219
220     def expressionReceived(self, lst):
221         """Called when an expression (list, string, or int) is received.
222         """
223         raise NotImplementedError()
224
225
226     outgoingVocabulary = {
227         # Jelly Data Types
228         'None'           :  1,
229         'class'          :  2,
230         'dereference'    :  3,
231         'reference'      :  4,
232         'dictionary'     :  5,
233         'function'       :  6,
234         'instance'       :  7,
235         'list'           :  8,
236         'module'         :  9,
237         'persistent'     : 10,
238         'tuple'          : 11,
239         'unpersistable'  : 12,
240
241         # PB Data Types
242         'copy'           : 13,
243         'cache'          : 14,
244         'cached'         : 15,
245         'remote'         : 16,
246         'local'          : 17,
247         'lcache'         : 18,
248
249         # PB Protocol Messages
250         'version'        : 19,
251         'login'          : 20,
252         'password'       : 21,
253         'challenge'      : 22,
254         'logged_in'      : 23,
255         'not_logged_in'  : 24,
256         'cachemessage'   : 25,
257         'message'        : 26,
258         'answer'         : 27,
259         'error'          : 28,
260         'decref'         : 29,
261         'decache'        : 30,
262         'uncache'        : 31,
263         }
264
265     incomingVocabulary = {}
266     for k, v in outgoingVocabulary.items():
267         incomingVocabulary[v] = k
268
269     def __init__(self, isClient=1):
270         self.listStack = []
271         self.outgoingSymbols = copy.copy(self.outgoingVocabulary)
272         self.outgoingSymbolCount = 0
273         self.isClient = isClient
274
275     def sendEncoded(self, obj):
276         io = cStringIO.StringIO()
277         self._encode(obj, io.write)
278         value = io.getvalue()
279         self.transport.write(value)
280
281     def _encode(self, obj, write):
282         if isinstance(obj, (list, tuple)):
283             if len(obj) > SIZE_LIMIT:
284                 raise BananaError(
285                     "list/tuple is too long to send (%d)" % (len(obj),))
286             int2b128(len(obj), write)
287             write(LIST)
288             for elem in obj:
289                 self._encode(elem, write)
290         elif isinstance(obj, (int, long)):
291             if obj < self._smallestLongInt or obj > self._largestLongInt:
292                 raise BananaError(
293                     "int/long is too large to send (%d)" % (obj,))
294             if obj < self._smallestInt:
295                 int2b128(-obj, write)
296                 write(LONGNEG)
297             elif obj < 0:
298                 int2b128(-obj, write)
299                 write(NEG)
300             elif obj <= self._largestInt:
301                 int2b128(obj, write)
302                 write(INT)
303             else:
304                 int2b128(obj, write)
305                 write(LONGINT)
306         elif isinstance(obj, float):
307             write(FLOAT)
308             write(struct.pack("!d", obj))
309         elif isinstance(obj, str):
310             # TODO: an API for extending banana...
311             if self.currentDialect == "pb" and obj in self.outgoingSymbols:
312                 symbolID = self.outgoingSymbols[obj]
313                 int2b128(symbolID, write)
314                 write(VOCAB)
315             else:
316                 if len(obj) > SIZE_LIMIT:
317                     raise BananaError(
318                         "string is too long to send (%d)" % (len(obj),))
319                 int2b128(len(obj), write)
320                 write(STRING)
321                 write(obj)
322         else:
323             raise BananaError("could not send object: %r" % (obj,))
324
325
326 # For use from the interactive interpreter
327 _i = Banana()
328 _i.connectionMade()
329 _i._selectDialect("none")
330
331
332 def encode(lst):
333     """Encode a list s-expression."""
334     io = cStringIO.StringIO()
335     _i.transport = io
336     _i.sendEncoded(lst)
337     return io.getvalue()
338
339
340 def decode(st):
341     """
342     Decode a banana-encoded string.
343     """
344     l = []
345     _i.expressionReceived = l.append
346     try:
347         _i.dataReceived(st)
348     finally:
349         _i.buffer = ''
350         del _i.expressionReceived
351     return l[0]
Note: See TracBrowser for help on using the browser.