source file: p10/parser.py
file stats: 126 lines, 125 executed: 99.2% covered
   1. #!/usr/bin/env python
   2. 
   3. import base64
   4. 
   5. class parser:
   6.     """ Takes a raw line from the connection, tokenises it and then passes it on to an appropriate handler """
   7. 
   8.     _handlers = dict()
   9.     _maxclientnum = 0
  10. 
  11.     def __init__(self, maxclientnum):
  12.         self._handlers = dict()
  13.         self._maxclientnum = maxclientnum
  14. 
  15.     def registerHandler(self, token, handler):
  16.         """ Add a new handler for a particular token """
  17.         self._handlers[token] = handler
  18. 
  19.     def _passToHandler(self, origin, token, args):
  20.         """ Pass something parsed to the appropriate handler """
  21.         try:
  22.             self._handlers[token].handle(origin, args)
  23.         except KeyError:
  24.             raise ParseError("Unknown command", None)
  25. 
  26.     def _checkLineIsGood(self, string):
  27.         """ Check the raw line meets our various standards, tidy it up and return it """
  28.         # The standard requires we only accept strings ending in \r\n or \n
  29.         if (string[-1] != "\n"):
  30.             raise ParseError('Line endings were not as expected', string)
  31. 
  32.         # The standard places a limit on line lengths
  33.         if (len(string)) > 512:
  34.             raise ProtocolError('Line too long to be valid', string)
  35. 
  36.         # Trim our trailing whitespace/line endings
  37.         return string.rstrip()
  38. 
  39.     def _parseParams(self, params):
  40.         """ Further break up our body into individual parameters """
  41.         if params[0] == ":":
  42.             params = [params[1:]]
  43.         else:
  44.             params = params.split(" :", 1)
  45.             if len(params) == 1:
  46.                 last_arg = None
  47.             else:
  48.                 last_arg = params[1]
  49.             params = params[0].split(None)
  50.             if last_arg != None:
  51.                 params.append(last_arg)
  52.         return params
  53. 
  54.     def parsePreAuth(self, string, origin):
  55.         """ Parse strings before authentication is established """
  56.         # Tidy up our line
  57.         string = self._checkLineIsGood(string)
  58. 
  59.         # Break up into token and body
  60.         high_level_parts = string.split(None, 1)
  61.         command = high_level_parts[0]
  62.         if not command.isupper() and not command.isdigit():
  63.             raise ProtocolError('Command not in uppercase', string)
  64.         params = self._parseParams(high_level_parts[1])
  65. 
  66.         # If this is an invalid command, pass it upwards
  67.         try:
  68.             self._passToHandler(origin, command, params)
  69.         except ParseError, error:
  70.             raise ParseError(error.value, string)
  71. 
  72.     def parse(self, string):
  73.         """ Take a string, parse it into our internal form and pass it on """
  74.         # Tidy up our line
  75.         string = self._checkLineIsGood(string)
  76. 
  77.         # Break up into origin, token and body
  78.         high_level_parts = string.split(None, 2)
  79.         origin = base64.parseNumeric(high_level_parts[0], self._maxclientnum)
  80.         command = high_level_parts[1]
  81.         if not command.isupper() and not command.isdigit():
  82.             raise ProtocolError('Command not in uppercase', string)
  83.         if len(high_level_parts) > 2:
  84.             params = self._parseParams(high_level_parts[2])
  85.         else:
  86.             params = []
  87. 
  88.         # If this is an invalid command, pass it upwards
  89.         try:
  90.             self._passToHandler(origin, command, params)
  91.         except ParseError, error:
  92.             raise ParseError(error.value, string)
  93. 
  94.     def build(self, origin, token, args):
  95.         """ Build a string suitable for sending """
  96.         # If the last argument is "long", package it for sending
  97.         if len(args) > 0:
  98.             if args[-1].find(" ") > -1:
  99.                  build_last_arg = ":" + args[-1]
 100.                  build_args = args[0:-1] + build_last_arg.split(" ")
 101.             else:
 102.                  build_args = args
 103.         else:
 104.             build_args = []
 105.         # Build the line
 106.         # Future compatibility - only send \n
 107.         ret = base64.createNumeric(origin) + " " + token + " " + " ".join(build_args) + "\n"
 108. 
 109.         # Check we're not sending things which are protocol violations
 110.         if len(ret) > 512:
 111.             raise ProtocolError('Line too long to send')
 112.         if not token.isupper() and not token.isdigit():
 113.             raise ProtocolError('Command not in uppercase during build')
 114. 
 115.         return ret
 116. 
 117. class ParseError(Exception):
 118.     """ An exception thrown if a line can not be parsed """
 119. 
 120.     line = ""
 121. 
 122.     def __init__(self, value, line):
 123.         self.value = value
 124.         self.line = line
 125. 
 126.     def __str__(self):
 127.         return repr(self.value) + " on line " + self.line
 128. 
 129. class ProtocolError(Exception):
 130.     """ An exception if a line is a protocol violation """
 131.     pass