1 #!/usr/bin/python -u 2 # 3 # This tests custom input callbacks 4 # 5 import sys 6 import libxml2 7 try: 8 import StringIO 9 str_io = StringIO.StringIO 10 except: 11 import io 12 str_io = io.StringIO 13 14 # We implement a new scheme, py://strings/ that will reference this dictionary 15 pystrings = { 16 'catalogs/catalog.xml' : 17 '''<?xml version="1.0" encoding="utf-8"?> 18 <!DOCTYPE catalog PUBLIC "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN" "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd"> 19 <catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog"> 20 <rewriteSystem systemIdStartString="http://example.com/dtds/" rewritePrefix="../dtds/"/> 21 </catalog>''', 22 23 'xml/sample.xml' : 24 '''<?xml version="1.0" encoding="utf-8"?> 25 <!DOCTYPE root SYSTEM "http://example.com/dtds/sample.dtd"> 26 <root>&sample.entity;</root>''', 27 28 'dtds/sample.dtd' : 29 ''' 30 <!ELEMENT root (#PCDATA)> 31 <!ENTITY sample.entity "replacement text">''' 32 } 33 34 prefix = "py://strings/" 35 startURL = prefix + "xml/sample.xml" 36 catURL = prefix + "catalogs/catalog.xml" 37 38 def my_input_cb(URI): 39 if not(URI.startswith(prefix)): 40 return None 41 path = URI[len(prefix):] 42 if path not in pystrings: 43 return None 44 return str_io(pystrings[path]) 45 46 47 def run_test(desc, docpath, catalog, exp_status="verified", exp_err=[], test_callback=None, 48 root_name="root", root_content="replacement text"): 49 opts = libxml2.XML_PARSE_DTDLOAD | libxml2.XML_PARSE_NONET | libxml2.XML_PARSE_COMPACT 50 actual_err = [] 51 52 def my_global_error_cb(ctx, msg): 53 actual_err.append((-1, msg)) 54 def my_ctx_error_cb(arg, msg, severity, reserved): 55 actual_err.append((severity, msg)) 56 57 libxml2.registerErrorHandler(my_global_error_cb, None) 58 try: 59 parser = libxml2.createURLParserCtxt(docpath, opts) 60 parser.setErrorHandler(my_ctx_error_cb, None) 61 if catalog is not None: 62 parser.addLocalCatalog(catalog) 63 if test_callback is not None: 64 test_callback() 65 parser.parseDocument() 66 doc = parser.doc() 67 actual_status = "loaded" 68 e = doc.getRootElement() 69 if e.name == root_name and e.content == root_content: 70 actual_status = "verified" 71 doc.freeDoc() 72 except libxml2.parserError: 73 actual_status = "not loaded" 74 75 if actual_status != exp_status: 76 print("Test '%s' failed: expect status '%s', actual '%s'" % (desc, exp_status, actual_status)) 77 sys.exit(1) 78 elif actual_err != exp_err: 79 print("Test '%s' failed" % desc) 80 print("Expect errors:") 81 for s,m in exp_err: print(" [%2d] '%s'" % (s,m)) 82 print("Actual errors:") 83 for s,m in actual_err: print(" [%2d] '%s'" % (s,m)) 84 sys.exit(1) 85 86 87 # Check that we cannot read custom schema without custom callback 88 run_test(desc="Loading entity without custom callback", 89 docpath=startURL, catalog=None, 90 exp_status="not loaded", exp_err=[ 91 (-1, "I/O "), 92 (-1, "warning : "), 93 (-1, "failed to load external entity \"py://strings/xml/sample.xml\"\n") 94 ]) 95 96 # Register handler and try to load the same entity 97 libxml2.registerInputCallback(my_input_cb) 98 run_test(desc="Loading entity with custom callback", 99 docpath=startURL, catalog=None, 100 exp_status="loaded", exp_err=[ 101 (-1, "Attempt to load network entity http://example.com/dtds/sample.dtd"), 102 ( 4, "Entity 'sample.entity' not defined\n") 103 ]) 104 105 # Register a catalog (also accessible via pystr://) and retry 106 run_test(desc="Loading entity with custom callback and catalog", 107 docpath=startURL, catalog=catURL) 108 109 # Unregister custom callback when parser is already created 110 run_test(desc="Loading entity and unregistering callback", 111 docpath=startURL, catalog=catURL, 112 test_callback=lambda: libxml2.popInputCallbacks(), 113 exp_status="loaded", exp_err=[ 114 ( 3, "failed to load external entity \"py://strings/dtds/sample.dtd\"\n"), 115 ( 4, "Entity 'sample.entity' not defined\n") 116 ]) 117 118 # Try to load the document again 119 run_test(desc="Retry loading document after unregistering callback", 120 docpath=startURL, catalog=catURL, 121 exp_status="not loaded", exp_err=[ 122 (-1, "I/O "), 123 (-1, "warning : "), 124 (-1, "failed to load external entity \"py://strings/xml/sample.xml\"\n") 125 ]) 126 127 # But should be able to read standard I/O yet... 128 run_test(desc="Loading using standard i/o after unregistering callback", 129 docpath="tst.xml", catalog=None, 130 root_name='doc', root_content='bar') 131 132 # Now pop ALL input callbacks, should fail to load even standard I/O 133 try: 134 while True: 135 libxml2.popInputCallbacks() 136 except IndexError: 137 pass 138 139 run_test(desc="Loading using standard i/o after unregistering all callbacks", 140 docpath="tst.xml", catalog=None, 141 exp_status="not loaded", exp_err=[ 142 (-1, "I/O "), 143 (-1, "warning : "), 144 (-1, "failed to load external entity \"tst.xml\"\n") 145 ]) 146 147 print("OK") 148 sys.exit(0); 149