RPKI Engine 1.0

xml_utils.py (3725)

Go to the documentation of this file.
00001 """
00002 XML utilities.
00003 
00004 $Id: xml_utils.py 3725 2011-03-18 03:51:12Z sra $
00005 
00006 Copyright (C) 2009-2011  Internet Systems Consortium ("ISC")
00007 
00008 Permission to use, copy, modify, and distribute this software for any
00009 purpose with or without fee is hereby granted, provided that the above
00010 copyright notice and this permission notice appear in all copies.
00011 
00012 THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
00013 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00014 AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
00015 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00016 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00017 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00018 PERFORMANCE OF THIS SOFTWARE.
00019 
00020 Portions copyright (C) 2007--2008  American Registry for Internet Numbers ("ARIN")
00021 
00022 Permission to use, copy, modify, and distribute this software for any
00023 purpose with or without fee is hereby granted, provided that the above
00024 copyright notice and this permission notice appear in all copies.
00025 
00026 THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
00027 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00028 AND FITNESS.  IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
00029 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00030 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00031 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00032 PERFORMANCE OF THIS SOFTWARE.
00033 """
00034 
00035 import xml.sax, lxml.sax, lxml.etree
00036 import rpki.exceptions
00037 
00038 class sax_handler(xml.sax.handler.ContentHandler):
00039   """
00040   SAX handler for RPKI protocols.
00041 
00042   This class provides some basic amenities for parsing protocol XML of
00043   the kind we use in the RPKI protocols, including whacking all the
00044   protocol element text into US-ASCII, simplifying accumulation of
00045   text fields, and hiding some of the fun relating to XML namespaces.
00046 
00047   General assumption: by the time this parsing code gets invoked, the
00048   XML has already passed RelaxNG validation, so we only have to check
00049   for errors that the schema can't catch, and we don't have to play as
00050   many XML namespace games.
00051   """
00052 
00053   def __init__(self):
00054     """
00055     Initialize SAX handler.
00056     """
00057     xml.sax.handler.ContentHandler.__init__(self)
00058     self.text = ""
00059     self.stack = []
00060 
00061   def startElementNS(self, name, qname, attrs):
00062     """
00063     Redirect startElementNS() events to startElement().
00064     """
00065     return self.startElement(name[1], attrs)
00066 
00067   def endElementNS(self, name, qname):
00068     """
00069     Redirect endElementNS() events to endElement().
00070     """
00071     return self.endElement(name[1])
00072 
00073   def characters(self, content):
00074     """
00075     Accumulate a chuck of element content (text).
00076     """
00077     self.text += content
00078 
00079   def startElement(self, name, attrs):
00080     """
00081     Handle startElement() events.
00082 
00083     We maintain a stack of nested elements under construction so that
00084     we can feed events directly to the current element rather than
00085     having to pass them through all the nesting elements.
00086 
00087     If the stack is empty, this event is for the outermost element, so
00088     we call a virtual method to create the corresponding object and
00089     that's the object we'll be returning as our final result.
00090     """
00091 
00092     a = dict()
00093     for k, v in attrs.items():
00094       if isinstance(k, tuple):
00095         if k == ("http://www.w3.org/XML/1998/namespace", "lang"):
00096           k = "xml:lang"
00097         else:
00098           assert k[0] is None
00099           k = k[1]
00100       a[k.encode("ascii")] = v.encode("ascii")
00101     if len(self.stack) == 0:
00102       assert not hasattr(self, "result")
00103       self.result = self.create_top_level(name, a)
00104       self.stack.append(self.result)
00105     self.stack[-1].startElement(self.stack, name, a)
00106 
00107   def endElement(self, name):
00108     """
00109     Handle endElement() events.  Mostly this means handling any
00110     accumulated element text.
00111     """
00112     text = self.text.encode("ascii").strip()
00113     self.text = ""
00114     self.stack[-1].endElement(self.stack, name, text)
00115 
00116   @classmethod
00117   def saxify(cls, elt):
00118     """
00119     Create a one-off SAX parser, parse an ETree, return the result.
00120     """
00121     self = cls()
00122     lxml.sax.saxify(elt, self)
00123     return self.result
00124 
00125   def create_top_level(self, name, attrs):
00126     """
00127     Handle top-level PDU for this protocol.
00128     """
00129     assert name == self.name and attrs["version"] == self.version
00130     return self.pdu()
00131 
00132 class base_elt(object):
00133   """
00134   Virtual base class for XML message elements.  The left-right and
00135   publication protocols use this.  At least for now, the up-down
00136   protocol does not, due to different design assumptions.
00137   """
00138 
00139   ## @var attributes
00140   # XML attributes for this element.
00141   attributes = ()
00142 
00143   ## @var elements
00144   # XML elements contained by this element.
00145   elements = ()
00146 
00147   ## @var booleans
00148   # Boolean attributes (value "yes" or "no") for this element.
00149   booleans = ()
00150 
00151   def startElement(self, stack, name, attrs):
00152     """
00153     Default startElement() handler: just process attributes.
00154     """
00155     if name not in self.elements:
00156       assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack)
00157       self.read_attrs(attrs)
00158 
00159   def endElement(self, stack, name, text):
00160     """
00161     Default endElement() handler: just pop the stack.
00162     """
00163     assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack)
00164     stack.pop()
00165 
00166   def toXML(self):
00167     """
00168     Default toXML() element generator.
00169     """
00170     return self.make_elt()
00171 
00172   def read_attrs(self, attrs):
00173     """
00174     Template-driven attribute reader.
00175     """
00176     for key in self.attributes:
00177       val = attrs.get(key, None)
00178       if isinstance(val, str) and val.isdigit() and not key.endswith("_handle"):
00179         val = long(val)
00180       setattr(self, key, val)
00181     for key in self.booleans:
00182       setattr(self, key, attrs.get(key, False))
00183 
00184   def make_elt(self):
00185     """
00186     XML element constructor.
00187     """
00188     elt = lxml.etree.Element("{%s}%s" % (self.xmlns, self.element_name), nsmap = self.nsmap)
00189     for key in self.attributes:
00190       val = getattr(self, key, None)
00191       if val is not None:
00192         elt.set(key, str(val))
00193     for key in self.booleans:
00194       if getattr(self, key, False):
00195         elt.set(key, "yes")
00196     return elt
00197 
00198   def make_b64elt(self, elt, name, value):
00199     """
00200     Constructor for Base64-encoded subelement.
00201     """
00202     if value is not None and not value.empty():
00203       lxml.etree.SubElement(elt, "{%s}%s" % (self.xmlns, name), nsmap = self.nsmap).text = value.get_Base64()
00204 
00205   def __str__(self):
00206     """
00207     Convert a base_elt object to string format.
00208     """
00209     lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "us-ascii")
00210 
00211   @classmethod
00212   def make_pdu(cls, **kargs):
00213     """
00214     Generic PDU constructor.
00215     """
00216     self = cls()
00217     for k, v in kargs.items():
00218       if isinstance(v, bool):
00219         v = 1 if v else 0
00220       setattr(self, k, v)
00221     return self
00222 
00223 class text_elt(base_elt):
00224   """
00225   Virtual base class for XML message elements that contain text.
00226   """
00227 
00228   ## @var text_attribute
00229   # Name of the class attribute that holds the text value.
00230   text_attribute = None
00231 
00232   def endElement(self, stack, name, text):
00233     """
00234     Extract text from parsed XML.
00235     """
00236     base_elt.endElement(self, stack, name, text)
00237     setattr(self, self.text_attribute, text)
00238 
00239   def toXML(self):
00240     """
00241     Insert text into generated XML.
00242     """
00243     elt = self.make_elt()
00244     elt.text = getattr(self, self.text_attribute) or None
00245     return elt
00246 
00247 class data_elt(base_elt):
00248   """
00249   Virtual base class for PDUs that map to SQL objects.  These objects
00250   all implement the create/set/get/list/destroy action attribute.
00251   """
00252 
00253   def endElement(self, stack, name, text):
00254     """
00255     Default endElement handler for SQL-based objects.  This assumes
00256     that sub-elements are Base64-encoded using the sql_template
00257     mechanism.
00258     """
00259     if name in self.elements:
00260       elt_type = self.sql_template.map.get(name)
00261       assert elt_type is not None, "Couldn't find element type for %s, stack %s" % (name, stack)
00262       setattr(self, name, elt_type(Base64 = text))
00263     else:
00264       assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack)
00265       stack.pop()
00266 
00267   def toXML(self):
00268     """
00269     Default element generator for SQL-based objects.  This assumes
00270     that sub-elements are Base64-encoded DER objects.
00271     """
00272     elt = self.make_elt()
00273     for i in self.elements:
00274       self.make_b64elt(elt, i, getattr(self, i, None))
00275     return elt
00276 
00277   def make_reply(self, r_pdu = None):
00278     """
00279     Construct a reply PDU.
00280     """
00281     if r_pdu is None:
00282       r_pdu = self.__class__()
00283       self.make_reply_clone_hook(r_pdu)
00284       handle_name = self.element_name + "_handle"
00285       setattr(r_pdu, handle_name, getattr(self, handle_name, None))
00286     else:
00287       self.make_reply_clone_hook(r_pdu)
00288       for b in r_pdu.booleans:
00289         setattr(r_pdu, b, False)
00290     r_pdu.action = self.action
00291     r_pdu.tag = self.tag
00292     return r_pdu
00293 
00294   def make_reply_clone_hook(self, r_pdu):
00295     """
00296     Overridable hook.
00297     """
00298     pass
00299 
00300   def serve_fetch_one(self):
00301     """
00302     Find the object on which a get, set, or destroy method should
00303     operate.
00304     """
00305     r = self.serve_fetch_one_maybe()
00306     if r is None:
00307       raise rpki.exceptions.NotFound
00308     return r
00309 
00310   def serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb):
00311     """
00312     Overridable hook.
00313     """
00314     cb()
00315 
00316   def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
00317     """
00318     Overridable hook.
00319     """
00320     cb()
00321 
00322   def serve_create(self, r_msg, cb, eb):
00323     """
00324     Handle a create action.
00325     """
00326 
00327     r_pdu = self.make_reply()
00328 
00329     def one():
00330       self.sql_store()
00331       setattr(r_pdu, self.sql_template.index, getattr(self, self.sql_template.index))
00332       self.serve_post_save_hook(self, r_pdu, two, eb)
00333 
00334     def two():
00335       r_msg.append(r_pdu)
00336       cb()
00337 
00338     oops = self.serve_fetch_one_maybe()
00339     if oops is not None:
00340       raise rpki.exceptions.DuplicateObject, "Object already exists: %r[%r] %r[%r]" % (self, getattr(self, self.element_name + "_handle"),
00341                                                                                        oops, getattr(oops, oops.element_name + "_handle"))
00342 
00343     self.serve_pre_save_hook(self, r_pdu, one, eb)
00344 
00345   def serve_set(self, r_msg, cb, eb):
00346     """
00347     Handle a set action.
00348     """
00349 
00350     db_pdu = self.serve_fetch_one()
00351     r_pdu = self.make_reply()
00352     for a in db_pdu.sql_template.columns[1:]:
00353       v = getattr(self, a, None)
00354       if v is not None:
00355         setattr(db_pdu, a, v)
00356     db_pdu.sql_mark_dirty()
00357 
00358     def one():
00359       db_pdu.sql_store()
00360       db_pdu.serve_post_save_hook(self, r_pdu, two, eb)
00361 
00362     def two():
00363       r_msg.append(r_pdu)
00364       cb()
00365 
00366     db_pdu.serve_pre_save_hook(self, r_pdu, one, eb)
00367 
00368   def serve_get(self, r_msg, cb, eb):
00369     """
00370     Handle a get action.
00371     """
00372     r_pdu = self.serve_fetch_one()
00373     self.make_reply(r_pdu)
00374     r_msg.append(r_pdu)
00375     cb()
00376 
00377   def serve_list(self, r_msg, cb, eb):
00378     """
00379     Handle a list action for non-self objects.
00380     """
00381     for r_pdu in self.serve_fetch_all():
00382       self.make_reply(r_pdu)
00383       r_msg.append(r_pdu)
00384     cb()
00385 
00386   def serve_destroy_hook(self, cb, eb):
00387     """
00388     Overridable hook.
00389     """
00390     cb()
00391 
00392   def serve_destroy(self, r_msg, cb, eb):
00393     """
00394     Handle a destroy action.
00395     """
00396     def done():
00397       db_pdu.sql_delete()
00398       r_msg.append(self.make_reply())
00399       cb()
00400     db_pdu = self.serve_fetch_one()
00401     db_pdu.serve_destroy_hook(done, eb)
00402 
00403   def serve_dispatch(self, r_msg, cb, eb):
00404     """
00405     Action dispatch handler.
00406     """
00407     dispatch = { "create"  : self.serve_create,
00408                  "set"     : self.serve_set,
00409                  "get"     : self.serve_get,
00410                  "list"    : self.serve_list,
00411                  "destroy" : self.serve_destroy }
00412     if self.action not in dispatch:
00413       raise rpki.exceptions.BadQuery, "Unexpected query: action %s" % self.action
00414     dispatch[self.action](r_msg, cb, eb)
00415   
00416   def unimplemented_control(self, *controls):
00417     """
00418     Uniform handling for unimplemented control operations.
00419     """
00420     unimplemented = [x for x in controls if getattr(self, x, False)]
00421     if unimplemented:
00422       raise rpki.exceptions.NotImplementedYet, "Unimplemented control %s" % ", ".join(unimplemented)
00423 
00424 class msg(list):
00425   """
00426   Generic top-level PDU.
00427   """
00428 
00429   def startElement(self, stack, name, attrs):
00430     """
00431     Handle top-level PDU.
00432     """
00433     if name == "msg":
00434       assert self.version == int(attrs["version"])
00435       self.type = attrs["type"]
00436     else:
00437       elt = self.pdus[name]()
00438       self.append(elt)
00439       stack.append(elt)
00440       elt.startElement(stack, name, attrs)
00441 
00442   def endElement(self, stack, name, text):
00443     """
00444     Handle top-level PDU.
00445     """
00446     assert name == "msg", "Unexpected name %s, stack %s" % (name, stack)
00447     assert len(stack) == 1
00448     stack.pop()
00449 
00450   def __str__(self):
00451     """
00452     Convert msg object to string.
00453     """
00454     lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "us-ascii")
00455 
00456   def toXML(self):
00457     """
00458     Generate top-level PDU.
00459     """
00460     elt = lxml.etree.Element("{%s}msg" % (self.xmlns), nsmap = self.nsmap, version = str(self.version), type = self.type)
00461     elt.extend([i.toXML() for i in self])
00462     return elt
00463 
00464   @classmethod
00465   def query(cls, *args):
00466     """
00467     Create a query PDU.
00468     """
00469     self = cls(args)
00470     self.type = "query"
00471     return self
00472 
00473   @classmethod
00474   def reply(cls, *args):
00475     """
00476     Create a reply PDU.
00477     """
00478     self = cls(args)
00479     self.type = "reply"
00480     return self
00481 
00482   def is_query(self):
00483     """
00484     Is this msg a query?
00485     """
00486     return self.type == "query"
00487 
00488   def is_reply(self):
00489     """
00490     Is this msg a reply?
00491     """
00492     return self.type == "reply"
 All Classes Namespaces Files Functions Variables