RPKI Engine
1.0
|
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"