~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/protocols/dict.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2004-06-21 22:01:11 UTC
  • mto: (2.2.3 sid)
  • mto: This revision was merged to the branch mainline in revision 3.
  • Revision ID: james.westby@ubuntu.com-20040621220111-vkf909euqnyrp3nr
Tags: upstream-1.3.0
ImportĀ upstreamĀ versionĀ 1.3.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Twisted, the Framework of Your Internet
 
2
# Copyright (C) 2001 Matthew W. Lefkowitz
 
3
#
 
4
# This library is free software; you can redistribute it and/or
 
5
# modify it under the terms of version 2.1 of the GNU Lesser General Public
 
6
# License as published by the Free Software Foundation.
 
7
#
 
8
# This library is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
11
# Lesser General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU Lesser General Public
 
14
# License along with this library; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""
 
18
Dict client protocol implementation.
 
19
 
 
20
@author: U{Pavel Pergamenshchik<mailto:pp64@cornell.edu>}
 
21
"""
 
22
 
 
23
from twisted.protocols import basic
 
24
from twisted.internet import defer, protocol
 
25
from twisted.python import log
 
26
from StringIO import StringIO
 
27
 
 
28
def parseParam(line):
 
29
    """Chew one dqstring or atom from beginning of line and return (param, remaningline)"""
 
30
    if line == '':
 
31
        return (None, '')
 
32
    elif line[0] != '"': # atom
 
33
        mode = 1
 
34
    else: # dqstring
 
35
        mode = 2
 
36
    res = ""
 
37
    io = StringIO(line)
 
38
    if mode == 2: # skip the opening quote
 
39
        io.read(1)
 
40
    while 1:
 
41
        a = io.read(1)
 
42
        if a == '"':
 
43
            if mode == 2:
 
44
                io.read(1) # skip the separating space
 
45
                return (res, io.read())
 
46
        elif a == '\\':
 
47
            a = io.read(1)
 
48
            if a == '':
 
49
                return (None, line) # unexpected end of string
 
50
        elif a == '':
 
51
            if mode == 1:
 
52
                return (res, io.read())
 
53
            else:
 
54
                return (None, line) # unexpected end of string
 
55
        elif a == ' ':
 
56
            if mode == 1:
 
57
                return (res, io.read())
 
58
        res += a
 
59
 
 
60
def makeAtom(line):
 
61
    """Munch a string into an 'atom'"""
 
62
    # FIXME: proper quoting
 
63
    return filter(lambda x: not (x in map(chr, range(33)+[34, 39, 92])), line)
 
64
 
 
65
def makeWord(s):
 
66
    mustquote = range(33)+[34, 39, 92]
 
67
    result = []
 
68
    for c in s:
 
69
        if ord(c) in mustquote:
 
70
            result.append("\\")
 
71
        result.append(c)
 
72
    s = "".join(result)
 
73
    return s
 
74
 
 
75
def parseText(line):
 
76
    if len(line) == 1 and line == '.':
 
77
        return None
 
78
    else:
 
79
        if len(line) > 1 and line[0:2] == '..':
 
80
            line = line[1:]
 
81
        return line
 
82
 
 
83
class Definition:
 
84
    """A word definition"""
 
85
    def __init__(self, name, db, dbdesc, text):
 
86
        self.name = name
 
87
        self.db = db
 
88
        self.dbdesc = dbdesc
 
89
        self.text = text # list of strings not terminated by newline
 
90
 
 
91
class DictClient(basic.LineReceiver):
 
92
    """dict (RFC2229) client"""
 
93
 
 
94
    data = None # multiline data
 
95
    MAX_LENGTH = 1024
 
96
    state = None
 
97
    mode = None
 
98
    result = None
 
99
    factory = None
 
100
 
 
101
    def __init__(self):
 
102
        self.data = None
 
103
        self.result = None
 
104
 
 
105
    def connectionMade(self):
 
106
        self.state = "conn"
 
107
        self.mode = "command"
 
108
 
 
109
    def sendLine(self, line):
 
110
        """Throw up if the line is longer than 1022 characters"""
 
111
        if len(line) > self.MAX_LENGTH - 2:
 
112
            raise ValueError("DictClient tried to send a too long line")
 
113
        basic.LineReceiver.sendLine(self, line)
 
114
 
 
115
    def lineReceived(self, line):
 
116
        try:
 
117
            line = line.decode("UTF-8")
 
118
        except UnicodeError: # garbage received, skip
 
119
            return
 
120
        if self.mode == "text": # we are receiving textual data
 
121
            code = "text"
 
122
        else:
 
123
            if len(line) < 4:
 
124
                log.msg("DictClient got invalid line from server -- %s" % line)
 
125
                self.protocolError("Invalid line from server")
 
126
                self.transport.LoseConnection()
 
127
                return
 
128
            code = int(line[:3])
 
129
            line = line[4:]
 
130
        method = getattr(self, 'dictCode_%s_%s' % (code, self.state), self.dictCode_default)
 
131
        method(line)
 
132
 
 
133
    def dictCode_default(self, line):
 
134
        """Unkown message"""
 
135
        log.msg("DictClient got unexpected message from server -- %s" % line)
 
136
        self.protocolError("Unexpected server message")
 
137
        self.transport.loseConnection()
 
138
 
 
139
    def dictCode_221_ready(self, line):
 
140
        """We are about to get kicked off, do nothing"""
 
141
        pass
 
142
 
 
143
    def dictCode_220_conn(self, line):
 
144
        """Greeting message"""
 
145
        self.state = "ready"
 
146
        self.dictConnected()
 
147
 
 
148
    def dictCode_530_conn(self):
 
149
        self.protocolError("Access denied")
 
150
        self.transport.loseConnection()
 
151
 
 
152
    def dictCode_420_conn(self):
 
153
        self.protocolError("Server temporarily unavailable")
 
154
        self.transport.loseConnection()
 
155
 
 
156
    def dictCode_421_conn(self):
 
157
        self.protocolError("Server shutting down at operator request")
 
158
        self.transport.loseConnection()
 
159
 
 
160
    def sendDefine(self, database, word):
 
161
        """Send a dict DEFINE command"""
 
162
        assert self.state == "ready", "DictClient.sendDefine called when not in ready state"
 
163
        self.result = None  # these two are just in case. In "ready" state, result and data
 
164
        self.data = None    # should be None
 
165
        self.state = "define"
 
166
        command = "DEFINE %s %s" % (makeAtom(database.encode("UTF-8")), makeWord(word.encode("UTF-8")))
 
167
        self.sendLine(command)
 
168
 
 
169
    def sendMatch(self, database, strategy, word):
 
170
        """Send a dict MATCH command"""
 
171
        assert self.state == "ready", "DictClient.sendMatch called when not in ready state"
 
172
        self.result = None
 
173
        self.data = None
 
174
        self.state = "match"
 
175
        command = "MATCH %s %s %s" % (makeAtom(database), makeAtom(strategy), makeAtom(word))
 
176
        self.sendLine(command.encode("UTF-8"))
 
177
 
 
178
    def dictCode_550_define(self, line):
 
179
        """Invalid database"""
 
180
        self.mode = "ready"
 
181
        self.defineFailed("Invalid database")
 
182
 
 
183
    def dictCode_550_match(self, line):
 
184
        """Invalid database"""
 
185
        self.mode = "ready"
 
186
        self.matchFailed("Invalid database")
 
187
 
 
188
    def dictCode_551_match(self, line):
 
189
        """Invalid strategy"""
 
190
        self.mode = "ready"
 
191
        self.matchFailed("Invalid strategy")
 
192
 
 
193
    def dictCode_552_define(self, line):
 
194
        """No match"""
 
195
        self.mode = "ready"
 
196
        self.defineFailed("No match")
 
197
 
 
198
    def dictCode_552_match(self, line):
 
199
        """No match"""
 
200
        self.mode = "ready"
 
201
        self.matchFailed("No match")
 
202
 
 
203
    def dictCode_150_define(self, line):
 
204
        """n definitions retrieved"""
 
205
        self.result = []
 
206
 
 
207
    def dictCode_151_define(self, line):
 
208
        """Definition text follows"""
 
209
        self.mode = "text"
 
210
        (word, line) = parseParam(line)
 
211
        (db, line) = parseParam(line)
 
212
        (dbdesc, line) = parseParam(line)
 
213
        if not (word and db and dbdesc):
 
214
            self.protocolError("Invalid server response")
 
215
            self.transport.loseConnection()
 
216
        else:
 
217
            self.result.append(Definition(word, db, dbdesc, []))
 
218
            self.data = []
 
219
 
 
220
    def dictCode_152_match(self, line):
 
221
        """n matches found, text follows"""
 
222
        self.mode = "text"
 
223
        self.result = []
 
224
        self.data = []
 
225
 
 
226
    def dictCode_text_define(self, line):
 
227
        """A line of definition text received"""
 
228
        res = parseText(line)
 
229
        if res == None:
 
230
            self.mode = "command"
 
231
            self.result[-1].text = self.data
 
232
            self.data = None
 
233
        else:
 
234
            self.data.append(line)
 
235
 
 
236
    def dictCode_text_match(self, line):
 
237
        """One line of match text received"""
 
238
        def l(s):
 
239
            p1, t = parseParam(s)
 
240
            p2, t = parseParam(t)
 
241
            return (p1, p2)
 
242
        res = parseText(line)
 
243
        if res == None:
 
244
            self.mode = "command"
 
245
            self.result = map(l, self.data)
 
246
            self.data = None
 
247
        else:
 
248
            self.data.append(line)
 
249
 
 
250
    def dictCode_250_define(self, line):
 
251
        """ok"""
 
252
        t = self.result
 
253
        self.result = None
 
254
        self.state = "ready"
 
255
        self.defineDone(t)
 
256
 
 
257
    def dictCode_250_match(self, line):
 
258
        """ok"""
 
259
        t = self.result
 
260
        self.result = None
 
261
        self.state = "ready"
 
262
        self.matchDone(t)
 
263
    
 
264
    def protocolError(self, reason):
 
265
        """override to catch unexpected dict protocol conditions"""
 
266
        pass
 
267
 
 
268
    def dictConnected(self):
 
269
        """override to be notified when the server is ready to accept commands"""
 
270
        pass
 
271
 
 
272
    def defineFailed(self, reason):
 
273
        """override to catch reasonable failure responses to DEFINE"""
 
274
        pass
 
275
 
 
276
    def defineDone(self, result):
 
277
        """override to catch succesful DEFINE"""
 
278
        pass
 
279
    
 
280
    def matchFailed(self, reason):
 
281
        """override to catch resonable failure responses to MATCH"""
 
282
        pass
 
283
 
 
284
    def matchDone(self, result):
 
285
        """override to catch succesful MATCH"""
 
286
        pass
 
287
 
 
288
 
 
289
class InvalidResponse(Exception):
 
290
    pass
 
291
 
 
292
 
 
293
class DictLookup(DictClient):
 
294
    """Utility class for a single dict transaction. To be used with DictLookupFactory"""
 
295
 
 
296
    def protocolError(self, reason):
 
297
        if not self.factory.done:
 
298
            self.factory.d.errback(InvalidResponse(reason))
 
299
            self.factory.clientDone()
 
300
 
 
301
    def dictConnected(self):
 
302
        if self.factory.queryType == "define":
 
303
            apply(self.sendDefine, self.factory.param)
 
304
        elif self.factory.queryType == "match":
 
305
            apply(self.sendMatch, self.factory.param)
 
306
 
 
307
    def defineFailed(self, reason):
 
308
        self.factory.d.callback([])
 
309
        self.factory.clientDone()
 
310
        self.transport.loseConnection()
 
311
 
 
312
    def defineDone(self, result):
 
313
        self.factory.d.callback(result)
 
314
        self.factory.clientDone()
 
315
        self.transport.loseConnection()
 
316
 
 
317
    def matchFailed(self, reason):
 
318
        self.factory.d.callback([])
 
319
        self.factory.clientDone()
 
320
        self.transport.loseConnection()
 
321
 
 
322
    def matchDone(self, result):
 
323
        self.factory.d.callback(result)
 
324
        self.factory.clientDone()
 
325
        self.transport.loseConnection()
 
326
 
 
327
 
 
328
class DictLookupFactory(protocol.ClientFactory):
 
329
    """Utility factory for a single dict transaction"""
 
330
    protocol = DictLookup
 
331
    done = None
 
332
 
 
333
    def __init__(self, queryType, param, d):
 
334
        self.queryType = queryType
 
335
        self.param = param
 
336
        self.d = d
 
337
        self.done = 0
 
338
 
 
339
    def clientDone(self):
 
340
        """Called by client when done."""
 
341
        self.done = 1
 
342
        del self.d
 
343
    
 
344
    def clientConnectionFailed(self, connector, error):
 
345
        self.d.errback(error)
 
346
 
 
347
    def clientConnectionLost(self, connector, error):
 
348
        if not self.done:
 
349
            self.d.errback(error)
 
350
 
 
351
    def buildProtocol(self, addr):
 
352
        p = self.protocol()
 
353
        p.factory = self
 
354
        return p
 
355
 
 
356
 
 
357
def define(host, port, database, word):
 
358
    """Look up a word using a dict server"""
 
359
    d = defer.Deferred()
 
360
    factory = DictLookupFactory("define", (database, word), d)
 
361
    
 
362
    from twisted.internet import reactor
 
363
    reactor.connectTCP(host, port, factory)
 
364
    return d
 
365
 
 
366
def match(host, port, database, strategy, word):
 
367
    """Match a word using a dict server"""
 
368
    d = defer.Deferred()
 
369
    factory = DictLookupFactory("match", (database, strategy, word), d)
 
370
 
 
371
    from twisted.internet import reactor
 
372
    reactor.connectTCP(host, port, factory)
 
373
    return d
 
374