123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415 |
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
- """
- Dict client protocol implementation.
- @author: Pavel Pergamenshchik
- """
- from twisted.protocols import basic
- from twisted.internet import defer, protocol
- from twisted.python import log
- from io import BytesIO
- def parseParam(line):
- """Chew one dqstring or atom from beginning of line and return (param, remaningline)"""
- if line == b'':
- return (None, b'')
- elif line[0:1] != b'"': # atom
- mode = 1
- else: # dqstring
- mode = 2
- res = b""
- io = BytesIO(line)
- if mode == 2: # skip the opening quote
- io.read(1)
- while 1:
- a = io.read(1)
- if a == b'"':
- if mode == 2:
- io.read(1) # skip the separating space
- return (res, io.read())
- elif a == b'\\':
- a = io.read(1)
- if a == b'':
- return (None, line) # unexpected end of string
- elif a == b'':
- if mode == 1:
- return (res, io.read())
- else:
- return (None, line) # unexpected end of string
- elif a == b' ':
- if mode == 1:
- return (res, io.read())
- res += a
- def makeAtom(line):
- """Munch a string into an 'atom'"""
- # FIXME: proper quoting
- return filter(lambda x: not (x in map(chr, range(33)+[34, 39, 92])), line)
- def makeWord(s):
- mustquote = range(33)+[34, 39, 92]
- result = []
- for c in s:
- if ord(c) in mustquote:
- result.append(b"\\")
- result.append(c)
- s = b"".join(result)
- return s
- def parseText(line):
- if len(line) == 1 and line == b'.':
- return None
- else:
- if len(line) > 1 and line[0:2] == b'..':
- line = line[1:]
- return line
- class Definition:
- """A word definition"""
- def __init__(self, name, db, dbdesc, text):
- self.name = name
- self.db = db
- self.dbdesc = dbdesc
- self.text = text # list of strings not terminated by newline
- class DictClient(basic.LineReceiver):
- """dict (RFC2229) client"""
- data = None # multiline data
- MAX_LENGTH = 1024
- state = None
- mode = None
- result = None
- factory = None
- def __init__(self):
- self.data = None
- self.result = None
- def connectionMade(self):
- self.state = "conn"
- self.mode = "command"
- def sendLine(self, line):
- """Throw up if the line is longer than 1022 characters"""
- if len(line) > self.MAX_LENGTH - 2:
- raise ValueError("DictClient tried to send a too long line")
- basic.LineReceiver.sendLine(self, line)
- def lineReceived(self, line):
- try:
- line = line.decode("utf-8")
- except UnicodeError: # garbage received, skip
- return
- if self.mode == "text": # we are receiving textual data
- code = "text"
- else:
- if len(line) < 4:
- log.msg("DictClient got invalid line from server -- %s" % line)
- self.protocolError("Invalid line from server")
- self.transport.LoseConnection()
- return
- code = int(line[:3])
- line = line[4:]
- method = getattr(self, 'dictCode_%s_%s' % (code, self.state), self.dictCode_default)
- method(line)
- def dictCode_default(self, line):
- """Unknown message"""
- log.msg("DictClient got unexpected message from server -- %s" % line)
- self.protocolError("Unexpected server message")
- self.transport.loseConnection()
- def dictCode_221_ready(self, line):
- """We are about to get kicked off, do nothing"""
- pass
- def dictCode_220_conn(self, line):
- """Greeting message"""
- self.state = "ready"
- self.dictConnected()
- def dictCode_530_conn(self):
- self.protocolError("Access denied")
- self.transport.loseConnection()
- def dictCode_420_conn(self):
- self.protocolError("Server temporarily unavailable")
- self.transport.loseConnection()
- def dictCode_421_conn(self):
- self.protocolError("Server shutting down at operator request")
- self.transport.loseConnection()
- def sendDefine(self, database, word):
- """Send a dict DEFINE command"""
- assert self.state == "ready", "DictClient.sendDefine called when not in ready state"
- self.result = None # these two are just in case. In "ready" state, result and data
- self.data = None # should be None
- self.state = "define"
- command = "DEFINE %s %s" % (makeAtom(database.encode("UTF-8")), makeWord(word.encode("UTF-8")))
- self.sendLine(command)
- def sendMatch(self, database, strategy, word):
- """Send a dict MATCH command"""
- assert self.state == "ready", "DictClient.sendMatch called when not in ready state"
- self.result = None
- self.data = None
- self.state = "match"
- command = "MATCH %s %s %s" % (makeAtom(database), makeAtom(strategy), makeAtom(word))
- self.sendLine(command.encode("UTF-8"))
- def dictCode_550_define(self, line):
- """Invalid database"""
- self.mode = "ready"
- self.defineFailed("Invalid database")
- def dictCode_550_match(self, line):
- """Invalid database"""
- self.mode = "ready"
- self.matchFailed("Invalid database")
- def dictCode_551_match(self, line):
- """Invalid strategy"""
- self.mode = "ready"
- self.matchFailed("Invalid strategy")
- def dictCode_552_define(self, line):
- """No match"""
- self.mode = "ready"
- self.defineFailed("No match")
- def dictCode_552_match(self, line):
- """No match"""
- self.mode = "ready"
- self.matchFailed("No match")
- def dictCode_150_define(self, line):
- """n definitions retrieved"""
- self.result = []
- def dictCode_151_define(self, line):
- """Definition text follows"""
- self.mode = "text"
- (word, line) = parseParam(line)
- (db, line) = parseParam(line)
- (dbdesc, line) = parseParam(line)
- if not (word and db and dbdesc):
- self.protocolError("Invalid server response")
- self.transport.loseConnection()
- else:
- self.result.append(Definition(word, db, dbdesc, []))
- self.data = []
- def dictCode_152_match(self, line):
- """n matches found, text follows"""
- self.mode = "text"
- self.result = []
- self.data = []
- def dictCode_text_define(self, line):
- """A line of definition text received"""
- res = parseText(line)
- if res == None:
- self.mode = "command"
- self.result[-1].text = self.data
- self.data = None
- else:
- self.data.append(line)
- def dictCode_text_match(self, line):
- """One line of match text received"""
- def l(s):
- p1, t = parseParam(s)
- p2, t = parseParam(t)
- return (p1, p2)
- res = parseText(line)
- if res == None:
- self.mode = "command"
- self.result = map(l, self.data)
- self.data = None
- else:
- self.data.append(line)
- def dictCode_250_define(self, line):
- """ok"""
- t = self.result
- self.result = None
- self.state = "ready"
- self.defineDone(t)
- def dictCode_250_match(self, line):
- """ok"""
- t = self.result
- self.result = None
- self.state = "ready"
- self.matchDone(t)
- def protocolError(self, reason):
- """override to catch unexpected dict protocol conditions"""
- pass
- def dictConnected(self):
- """override to be notified when the server is ready to accept commands"""
- pass
- def defineFailed(self, reason):
- """override to catch reasonable failure responses to DEFINE"""
- pass
- def defineDone(self, result):
- """override to catch successful DEFINE"""
- pass
- def matchFailed(self, reason):
- """override to catch resonable failure responses to MATCH"""
- pass
- def matchDone(self, result):
- """override to catch successful MATCH"""
- pass
- class InvalidResponse(Exception):
- pass
- class DictLookup(DictClient):
- """Utility class for a single dict transaction. To be used with DictLookupFactory"""
- def protocolError(self, reason):
- if not self.factory.done:
- self.factory.d.errback(InvalidResponse(reason))
- self.factory.clientDone()
- def dictConnected(self):
- if self.factory.queryType == "define":
- self.sendDefine(*self.factory.param)
- elif self.factory.queryType == "match":
- self.sendMatch(*self.factory.param)
- def defineFailed(self, reason):
- self.factory.d.callback([])
- self.factory.clientDone()
- self.transport.loseConnection()
- def defineDone(self, result):
- self.factory.d.callback(result)
- self.factory.clientDone()
- self.transport.loseConnection()
- def matchFailed(self, reason):
- self.factory.d.callback([])
- self.factory.clientDone()
- self.transport.loseConnection()
- def matchDone(self, result):
- self.factory.d.callback(result)
- self.factory.clientDone()
- self.transport.loseConnection()
- class DictLookupFactory(protocol.ClientFactory):
- """Utility factory for a single dict transaction"""
- protocol = DictLookup
- done = None
- def __init__(self, queryType, param, d):
- self.queryType = queryType
- self.param = param
- self.d = d
- self.done = 0
- def clientDone(self):
- """Called by client when done."""
- self.done = 1
- del self.d
- def clientConnectionFailed(self, connector, error):
- self.d.errback(error)
- def clientConnectionLost(self, connector, error):
- if not self.done:
- self.d.errback(error)
- def buildProtocol(self, addr):
- p = self.protocol()
- p.factory = self
- return p
- def define(host, port, database, word):
- """Look up a word using a dict server"""
- d = defer.Deferred()
- factory = DictLookupFactory("define", (database, word), d)
- from twisted.internet import reactor
- reactor.connectTCP(host, port, factory)
- return d
- def match(host, port, database, strategy, word):
- """Match a word using a dict server"""
- d = defer.Deferred()
- factory = DictLookupFactory("match", (database, strategy, word), d)
- from twisted.internet import reactor
- reactor.connectTCP(host, port, factory)
- return d
|