00001 """
00002 XML utilities.
00003
00004 $Id: xml_utils.py 2836 2009-10-26 22:31:16Z sra $
00005
00006 Copyright (C) 2009 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, base64
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 """Redirect startElementNS() events to startElement()."""
00063 return self.startElement(name[1], attrs)
00064
00065 def endElementNS(self, name, qname):
00066 """Redirect endElementNS() events to endElement()."""
00067 return self.endElement(name[1])
00068
00069 def characters(self, content):
00070 """Accumulate a chuck of element content (text)."""
00071 self.text += content
00072
00073 def startElement(self, name, attrs):
00074 """
00075 Handle startElement() events.
00076
00077 We maintain a stack of nested elements under construction so that
00078 we can feed events directly to the current element rather than
00079 having to pass them through all the nesting elements.
00080
00081 If the stack is empty, this event is for the outermost element, so
00082 we call a virtual method to create the corresponding object and
00083 that's the object we'll be returning as our final result.
00084 """
00085
00086 a = dict()
00087 for k, v in attrs.items():
00088 if isinstance(k, tuple):
00089 if k == ("http://www.w3.org/XML/1998/namespace", "lang"):
00090 k = "xml:lang"
00091 else:
00092 assert k[0] is None
00093 k = k[1]
00094 a[k.encode("ascii")] = v.encode("ascii")
00095 if len(self.stack) == 0:
00096 assert not hasattr(self, "result")
00097 self.result = self.create_top_level(name, a)
00098 self.stack.append(self.result)
00099 self.stack[-1].startElement(self.stack, name, a)
00100
00101 def endElement(self, name):
00102 """
00103 Handle endElement() events. Mostly this means handling any
00104 accumulated element text.
00105 """
00106 text = self.text.encode("ascii").strip()
00107 self.text = ""
00108 self.stack[-1].endElement(self.stack, name, text)
00109
00110 @classmethod
00111 def saxify(cls, elt):
00112 """
00113 Create a one-off SAX parser, parse an ETree, return the result.
00114 """
00115 self = cls()
00116 lxml.sax.saxify(elt, self)
00117 return self.result
00118
00119 def create_top_level(self, name, attrs):
00120 """
00121 Handle top-level PDU for this protocol.
00122 """
00123 assert name == self.name and attrs["version"] == self.version
00124 return self.pdu()
00125
00126 class base_elt(object):
00127 """
00128 Virtual base class for XML message elements. The left-right and
00129 publication protocols use this. At least for now, the up-down
00130 protocol does not, due to different design assumptions.
00131 """
00132
00133
00134
00135 attributes = ()
00136
00137
00138
00139 elements = ()
00140
00141
00142
00143 booleans = ()
00144
00145 def startElement(self, stack, name, attrs):
00146 """
00147 Default startElement() handler: just process attributes.
00148 """
00149 if name not in self.elements:
00150 assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack)
00151 self.read_attrs(attrs)
00152
00153 def endElement(self, stack, name, text):
00154 """
00155 Default endElement() handler: just pop the stack.
00156 """
00157 assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack)
00158 stack.pop()
00159
00160 def toXML(self):
00161 """
00162 Default toXML() element generator.
00163 """
00164 return self.make_elt()
00165
00166 def read_attrs(self, attrs):
00167 """
00168 Template-driven attribute reader.
00169 """
00170 for key in self.attributes:
00171 val = attrs.get(key, None)
00172 if isinstance(val, str) and val.isdigit() and not key.endswith("_handle"):
00173 val = long(val)
00174 setattr(self, key, val)
00175 for key in self.booleans:
00176 setattr(self, key, attrs.get(key, False))
00177
00178 def make_elt(self):
00179 """
00180 XML element constructor.
00181 """
00182 elt = lxml.etree.Element("{%s}%s" % (self.xmlns, self.element_name), nsmap = self.nsmap)
00183 for key in self.attributes:
00184 val = getattr(self, key, None)
00185 if val is not None:
00186 elt.set(key, str(val))
00187 for key in self.booleans:
00188 if getattr(self, key, False):
00189 elt.set(key, "yes")
00190 return elt
00191
00192 def make_b64elt(self, elt, name, value = None):
00193 """
00194 Constructor for Base64-encoded subelement.
00195 """
00196 if value is None:
00197 value = getattr(self, name, None)
00198 if value is not None:
00199 lxml.etree.SubElement(elt, "{%s}%s" % (self.xmlns, name), nsmap = self.nsmap).text = base64.b64encode(value)
00200
00201 def __str__(self):
00202 """
00203 Convert a base_elt object to string format.
00204 """
00205 lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "us-ascii")
00206
00207 @classmethod
00208 def make_pdu(cls, **kargs):
00209 """
00210 Generic PDU constructor.
00211 """
00212 self = cls()
00213 for k, v in kargs.items():
00214 if isinstance(v, bool):
00215 v = 1 if v else 0
00216 setattr(self, k, v)
00217 return self
00218
00219 class text_elt(base_elt):
00220 """
00221 Virtual base class for XML message elements that contain text.
00222 """
00223
00224
00225
00226 text_attribute = None
00227
00228 def endElement(self, stack, name, text):
00229 """
00230 Extract text from parsed XML.
00231 """
00232 base_elt.endElement(self, stack, name, text)
00233 setattr(self, self.text_attribute, text)
00234
00235 def toXML(self):
00236 """
00237 Insert text into generated XML.
00238 """
00239 elt = self.make_elt()
00240 elt.text = getattr(self, self.text_attribute) or None
00241 return elt
00242
00243 class data_elt(base_elt):
00244 """
00245 Virtual base class for PDUs that map to SQL objects. These objects
00246 all implement the create/set/get/list/destroy action attribute.
00247 """
00248
00249 def endElement(self, stack, name, text):
00250 """
00251 Default endElement handler for SQL-based objects. This assumes
00252 that sub-elements are Base64-encoded using the sql_template
00253 mechanism.
00254 """
00255 if name in self.elements:
00256 elt_type = self.sql_template.map.get(name)
00257 assert elt_type is not None, "Couldn't find element type for %s, stack %s" % (name, stack)
00258 setattr(self, name, elt_type(Base64 = text))
00259 else:
00260 assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack)
00261 stack.pop()
00262
00263 def toXML(self):
00264 """
00265 Default element generator for SQL-based objects. This assumes
00266 that sub-elements are Base64-encoded DER objects.
00267 """
00268 elt = self.make_elt()
00269 for i in self.elements:
00270 x = getattr(self, i, None)
00271 if x and not x.empty():
00272 self.make_b64elt(elt, i, x.get_DER())
00273 return elt
00274
00275 def make_reply(self, r_pdu = None):
00276 """
00277 Construct a reply PDU.
00278 """
00279 if r_pdu is None:
00280 r_pdu = self.__class__()
00281 self.make_reply_clone_hook(r_pdu)
00282 handle_name = self.element_name + "_handle"
00283 setattr(r_pdu, handle_name, getattr(self, handle_name, None))
00284 else:
00285 self.make_reply_clone_hook(r_pdu)
00286 for b in r_pdu.booleans:
00287 setattr(r_pdu, b, False)
00288 r_pdu.action = self.action
00289 r_pdu.tag = self.tag
00290 return r_pdu
00291
00292 def make_reply_clone_hook(self, r_pdu):
00293 """Overridable hook."""
00294 pass
00295
00296 def serve_fetch_one(self):
00297 """
00298 Find the object on which a get, set, or destroy method should
00299 operate.
00300 """
00301 r = self.serve_fetch_one_maybe()
00302 if r is None:
00303 raise rpki.exceptions.NotFound
00304 return r
00305
00306 def serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb):
00307 """Overridable hook."""
00308 cb()
00309
00310 def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
00311 """Overridable hook."""
00312 cb()
00313
00314 def serve_create(self, r_msg, cb, eb):
00315 """
00316 Handle a create action.
00317 """
00318
00319 r_pdu = self.make_reply()
00320
00321 def one():
00322 self.sql_store()
00323 setattr(r_pdu, self.sql_template.index, getattr(self, self.sql_template.index))
00324 self.serve_post_save_hook(self, r_pdu, two, eb)
00325
00326 def two():
00327 r_msg.append(r_pdu)
00328 cb()
00329
00330 oops = self.serve_fetch_one_maybe()
00331 if oops is not None:
00332 raise rpki.exceptions.DuplicateObject, "Object already exists: %r[%r] %r[%r]" % (self, getattr(self, self.element_name + "_handle"),
00333 oops, getattr(oops, oops.element_name + "_handle"))
00334
00335 self.serve_pre_save_hook(self, r_pdu, one, eb)
00336
00337 def serve_set(self, r_msg, cb, eb):
00338 """
00339 Handle a set action.
00340 """
00341
00342 db_pdu = self.serve_fetch_one()
00343 r_pdu = self.make_reply()
00344 for a in db_pdu.sql_template.columns[1:]:
00345 v = getattr(self, a, None)
00346 if v is not None:
00347 setattr(db_pdu, a, v)
00348 db_pdu.sql_mark_dirty()
00349
00350 def one():
00351 db_pdu.sql_store()
00352 db_pdu.serve_post_save_hook(self, r_pdu, two, eb)
00353
00354 def two():
00355 r_msg.append(r_pdu)
00356 cb()
00357
00358 db_pdu.serve_pre_save_hook(self, r_pdu, one, eb)
00359
00360 def serve_get(self, r_msg, cb, eb):
00361 """
00362 Handle a get action.
00363 """
00364 r_pdu = self.serve_fetch_one()
00365 self.make_reply(r_pdu)
00366 r_msg.append(r_pdu)
00367 cb()
00368
00369 def serve_list(self, r_msg, cb, eb):
00370 """
00371 Handle a list action for non-self objects.
00372 """
00373 for r_pdu in self.serve_fetch_all():
00374 self.make_reply(r_pdu)
00375 r_msg.append(r_pdu)
00376 cb()
00377
00378 def serve_destroy_hook(self, cb, eb):
00379 """
00380 Overridable hook.
00381 """
00382 cb()
00383
00384 def serve_destroy(self, r_msg, cb, eb):
00385 """
00386 Handle a destroy action.
00387 """
00388 def done():
00389 db_pdu.sql_delete()
00390 r_msg.append(self.make_reply())
00391 cb()
00392 db_pdu = self.serve_fetch_one()
00393 db_pdu.serve_destroy_hook(done, eb)
00394
00395 def serve_dispatch(self, r_msg, cb, eb):
00396 """
00397 Action dispatch handler.
00398 """
00399 dispatch = { "create" : self.serve_create,
00400 "set" : self.serve_set,
00401 "get" : self.serve_get,
00402 "list" : self.serve_list,
00403 "destroy" : self.serve_destroy }
00404 if self.action not in dispatch:
00405 raise rpki.exceptions.BadQuery, "Unexpected query: action %s" % self.action
00406 dispatch[self.action](r_msg, cb, eb)
00407
00408 def unimplemented_control(self, *controls):
00409 """
00410 Uniform handling for unimplemented control operations.
00411 """
00412 unimplemented = [x for x in controls if getattr(self, x, False)]
00413 if unimplemented:
00414 raise rpki.exceptions.NotImplementedYet, "Unimplemented control %s" % ", ".join(unimplemented)
00415
00416 class msg(list):
00417 """
00418 Generic top-level PDU.
00419 """
00420
00421 def startElement(self, stack, name, attrs):
00422 """
00423 Handle top-level PDU.
00424 """
00425 if name == "msg":
00426 assert self.version == int(attrs["version"])
00427 self.type = attrs["type"]
00428 else:
00429 elt = self.pdus[name]()
00430 self.append(elt)
00431 stack.append(elt)
00432 elt.startElement(stack, name, attrs)
00433
00434 def endElement(self, stack, name, text):
00435 """
00436 Handle top-level PDU.
00437 """
00438 assert name == "msg", "Unexpected name %s, stack %s" % (name, stack)
00439 assert len(stack) == 1
00440 stack.pop()
00441
00442 def __str__(self):
00443 """Convert msg object to string."""
00444 lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "us-ascii")
00445
00446 def toXML(self):
00447 """
00448 Generate top-level PDU.
00449 """
00450 elt = lxml.etree.Element("{%s}msg" % (self.xmlns), nsmap = self.nsmap, version = str(self.version), type = self.type)
00451 elt.extend([i.toXML() for i in self])
00452 return elt
00453
00454 @classmethod
00455 def query(cls, *args):
00456 """Create a query PDU."""
00457 self = cls(args)
00458 self.type = "query"
00459 return self
00460
00461 @classmethod
00462 def reply(cls, *args):
00463 """Create a reply PDU."""
00464 self = cls(args)
00465 self.type = "reply"
00466 return self
00467
00468 def is_query(self):
00469 """Is this msg a query?"""
00470 return self.type == "query"
00471
00472 def is_reply(self):
00473 """Is this msg a reply?"""
00474 return self.type == "reply"