1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.apache.harmony.xml; 18 19 import org.xml.sax.Attributes; 20 import org.xml.sax.ContentHandler; 21 import org.xml.sax.DTDHandler; 22 import org.xml.sax.EntityResolver; 23 import org.xml.sax.InputSource; 24 import org.xml.sax.Locator; 25 import org.xml.sax.SAXException; 26 import org.xml.sax.SAXParseException; 27 import org.xml.sax.ext.LexicalHandler; 28 29 import android.compat.annotation.UnsupportedAppUsage; 30 31 import java.io.IOException; 32 import java.io.InputStream; 33 import java.io.Reader; 34 import java.net.URI; 35 import java.net.URL; 36 import java.net.URLConnection; 37 import libcore.io.IoUtils; 38 39 import dalvik.annotation.optimization.ReachabilitySensitive; 40 41 /** 42 * Adapts SAX API to the Expat native XML parser. Not intended for reuse 43 * across documents. 44 * 45 * @see org.apache.harmony.xml.ExpatReader 46 */ 47 class ExpatParser { 48 49 private static final int BUFFER_SIZE = 8096; // in bytes 50 51 /** Pointer to XML_Parser instance. */ 52 // A few native methods taking the pointer value are static; @ReachabilitySensitive is 53 // necessary to ensure the Java object is kept reachable sufficiently long in these cases. 54 @ReachabilitySensitive 55 private long pointer; 56 57 private boolean inStartElement = false; 58 private int attributeCount = -1; 59 private long attributePointer = 0; 60 61 private final Locator locator = new ExpatLocator(); 62 63 @UnsupportedAppUsage 64 private final ExpatReader xmlReader; 65 66 private final String publicId; 67 private final String systemId; 68 69 private final String encoding; 70 71 @UnsupportedAppUsage 72 private final ExpatAttributes attributes = new CurrentAttributes(); 73 74 private static final String OUTSIDE_START_ELEMENT 75 = "Attributes can only be used within the scope of startElement()."; 76 77 /** We default to UTF-8 when the user doesn't specify an encoding. */ 78 private static final String DEFAULT_ENCODING = "UTF-8"; 79 80 /** Encoding used for Java chars, used to parse Readers and Strings */ 81 /*package*/ static final String CHARACTER_ENCODING = "UTF-16"; 82 83 /** Timeout for HTTP connections (in ms) */ 84 private static final int TIMEOUT = 20 * 1000; 85 86 /** 87 * Constructs a new parser with the specified encoding. 88 */ 89 @UnsupportedAppUsage ExpatParser(String encoding, ExpatReader xmlReader, boolean processNamespaces, String publicId, String systemId)90 /*package*/ ExpatParser(String encoding, ExpatReader xmlReader, 91 boolean processNamespaces, String publicId, String systemId) { 92 this.publicId = publicId; 93 this.systemId = systemId; 94 95 this.xmlReader = xmlReader; 96 97 /* 98 * TODO: Let Expat try to guess the encoding instead of defaulting. 99 * Unfortunately, I don't know how to tell which encoding Expat picked, 100 * so I won't know how to encode "<externalEntity>" below. The solution 101 * I think is to fix Expat to not require the "<externalEntity>" 102 * workaround. 103 */ 104 this.encoding = encoding == null ? DEFAULT_ENCODING : encoding; 105 this.pointer = initialize( 106 this.encoding, 107 processNamespaces 108 ); 109 } 110 111 /** 112 * Used by {@link EntityParser}. 113 */ ExpatParser(String encoding, ExpatReader xmlReader, long pointer, String publicId, String systemId)114 private ExpatParser(String encoding, ExpatReader xmlReader, long pointer, 115 String publicId, String systemId) { 116 this.encoding = encoding; 117 this.xmlReader = xmlReader; 118 this.pointer = pointer; 119 this.systemId = systemId; 120 this.publicId = publicId; 121 } 122 123 /** 124 * Initializes native resources. 125 * 126 * @return the pointer to the native parser 127 */ initialize(String encoding, boolean namespacesEnabled)128 private native long initialize(String encoding, boolean namespacesEnabled); 129 130 /** 131 * Called at the start of an element. 132 * 133 * @param uri namespace URI of element or "" if namespace processing is 134 * disabled 135 * @param localName local name of element or "" if namespace processing is 136 * disabled 137 * @param qName qualified name or "" if namespace processing is enabled 138 * @param attributePointer pointer to native attribute char*--we keep 139 * a separate pointer so we can detach it from the parser instance 140 * @param attributeCount number of attributes 141 */ startElement(String uri, String localName, String qName, long attributePointer, int attributeCount)142 /*package*/ void startElement(String uri, String localName, String qName, 143 long attributePointer, int attributeCount) throws SAXException { 144 ContentHandler contentHandler = xmlReader.contentHandler; 145 if (contentHandler == null) { 146 return; 147 } 148 149 try { 150 inStartElement = true; 151 this.attributePointer = attributePointer; 152 this.attributeCount = attributeCount; 153 154 contentHandler.startElement( 155 uri, localName, qName, this.attributes); 156 } finally { 157 inStartElement = false; 158 this.attributeCount = -1; 159 this.attributePointer = 0; 160 } 161 } 162 endElement(String uri, String localName, String qName)163 /*package*/ void endElement(String uri, String localName, String qName) 164 throws SAXException { 165 ContentHandler contentHandler = xmlReader.contentHandler; 166 if (contentHandler != null) { 167 contentHandler.endElement(uri, localName, qName); 168 } 169 } 170 text(char[] text, int length)171 /*package*/ void text(char[] text, int length) throws SAXException { 172 ContentHandler contentHandler = xmlReader.contentHandler; 173 if (contentHandler != null) { 174 contentHandler.characters(text, 0, length); 175 } 176 } 177 comment(char[] text, int length)178 /*package*/ void comment(char[] text, int length) throws SAXException { 179 LexicalHandler lexicalHandler = xmlReader.lexicalHandler; 180 if (lexicalHandler != null) { 181 lexicalHandler.comment(text, 0, length); 182 } 183 } 184 startCdata()185 /*package*/ void startCdata() throws SAXException { 186 LexicalHandler lexicalHandler = xmlReader.lexicalHandler; 187 if (lexicalHandler != null) { 188 lexicalHandler.startCDATA(); 189 } 190 } 191 endCdata()192 /*package*/ void endCdata() throws SAXException { 193 LexicalHandler lexicalHandler = xmlReader.lexicalHandler; 194 if (lexicalHandler != null) { 195 lexicalHandler.endCDATA(); 196 } 197 } 198 startNamespace(String prefix, String uri)199 /*package*/ void startNamespace(String prefix, String uri) 200 throws SAXException { 201 ContentHandler contentHandler = xmlReader.contentHandler; 202 if (contentHandler != null) { 203 contentHandler.startPrefixMapping(prefix, uri); 204 } 205 } 206 endNamespace(String prefix)207 /*package*/ void endNamespace(String prefix) throws SAXException { 208 ContentHandler contentHandler = xmlReader.contentHandler; 209 if (contentHandler != null) { 210 contentHandler.endPrefixMapping(prefix); 211 } 212 } 213 startDtd(String name, String publicId, String systemId)214 /*package*/ void startDtd(String name, String publicId, String systemId) 215 throws SAXException { 216 LexicalHandler lexicalHandler = xmlReader.lexicalHandler; 217 if (lexicalHandler != null) { 218 lexicalHandler.startDTD(name, publicId, systemId); 219 } 220 } 221 endDtd()222 /*package*/ void endDtd() throws SAXException { 223 LexicalHandler lexicalHandler = xmlReader.lexicalHandler; 224 if (lexicalHandler != null) { 225 lexicalHandler.endDTD(); 226 } 227 } 228 processingInstruction(String target, String data)229 /*package*/ void processingInstruction(String target, String data) 230 throws SAXException { 231 ContentHandler contentHandler = xmlReader.contentHandler; 232 if (contentHandler != null) { 233 contentHandler.processingInstruction(target, data); 234 } 235 } 236 notationDecl(String name, String publicId, String systemId)237 /*package*/ void notationDecl(String name, String publicId, String systemId) throws SAXException { 238 DTDHandler dtdHandler = xmlReader.dtdHandler; 239 if (dtdHandler != null) { 240 dtdHandler.notationDecl(name, publicId, systemId); 241 } 242 } 243 unparsedEntityDecl(String name, String publicId, String systemId, String notationName)244 /*package*/ void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) throws SAXException { 245 DTDHandler dtdHandler = xmlReader.dtdHandler; 246 if (dtdHandler != null) { 247 dtdHandler.unparsedEntityDecl(name, publicId, systemId, notationName); 248 } 249 } 250 251 /** 252 * Handles an external entity. 253 * 254 * @param context to be passed back to Expat when we parse the entity 255 * @param publicId the publicId of the entity 256 * @param systemId the systemId of the entity 257 */ handleExternalEntity(String context, String publicId, String systemId)258 /*package*/ void handleExternalEntity(String context, String publicId, 259 String systemId) throws SAXException, IOException { 260 EntityResolver entityResolver = xmlReader.entityResolver; 261 if (entityResolver == null) { 262 return; 263 } 264 265 /* 266 * The spec. is terribly under-specified here. It says that if the 267 * systemId is a URL, we should try to resolve it, but it doesn't 268 * specify how to tell whether or not the systemId is a URL let alone 269 * how to resolve it. 270 * 271 * Other implementations do various dangerous things. We try to keep it 272 * simple: if the systemId parses as a URI and it's relative, we try to 273 * resolve it against the parent document's systemId. If anything goes 274 * wrong, we go with the original systemId. If crazybob had designed 275 * the API, he would have left all resolving to the EntityResolver. 276 */ 277 if (this.systemId != null) { 278 try { 279 URI systemUri = new URI(systemId); 280 if (!systemUri.isAbsolute() && !systemUri.isOpaque()) { 281 // It could be relative (or it may not be a URI at all!) 282 URI baseUri = new URI(this.systemId); 283 systemUri = baseUri.resolve(systemUri); 284 285 // Replace systemId w/ resolved URI 286 systemId = systemUri.toString(); 287 } 288 } catch (Exception e) { 289 System.logI("Could not resolve '" + systemId + "' relative to" 290 + " '" + this.systemId + "' at " + locator, e); 291 } 292 } 293 294 InputSource inputSource = entityResolver.resolveEntity( 295 publicId, systemId); 296 if (inputSource == null) { 297 /* 298 * The spec. actually says that we should try to treat systemId 299 * as a URL and download and parse its contents here, but an 300 * entity resolver can easily accomplish the same by returning 301 * new InputSource(systemId). 302 * 303 * Downloading external entities by default would result in several 304 * unwanted DTD downloads, not to mention pose a security risk 305 * when parsing untrusted XML -- see for example 306 * http://archive.cert.uni-stuttgart.de/bugtraq/2002/10/msg00421.html -- 307 * so we just do nothing instead. This also enables the user to 308 * opt out of entity parsing when using 309 * {@link org.xml.sax.helpers.DefaultHandler}, something that 310 * wouldn't be possible otherwise. 311 */ 312 return; 313 } 314 315 String encoding = pickEncoding(inputSource); 316 long pointer = createEntityParser(this.pointer, context); 317 try { 318 EntityParser entityParser = new EntityParser(encoding, xmlReader, 319 pointer, inputSource.getPublicId(), 320 inputSource.getSystemId()); 321 322 parseExternalEntity(entityParser, inputSource); 323 } finally { 324 releaseParser(pointer); 325 } 326 } 327 328 /** 329 * Picks an encoding for an external entity. Defaults to UTF-8. 330 */ pickEncoding(InputSource inputSource)331 private String pickEncoding(InputSource inputSource) { 332 Reader reader = inputSource.getCharacterStream(); 333 if (reader != null) { 334 return CHARACTER_ENCODING; 335 } 336 337 String encoding = inputSource.getEncoding(); 338 return encoding == null ? DEFAULT_ENCODING : encoding; 339 } 340 341 /** 342 * Parses the the external entity provided by the input source. 343 */ parseExternalEntity(ExpatParser entityParser, InputSource inputSource)344 private void parseExternalEntity(ExpatParser entityParser, 345 InputSource inputSource) throws IOException, SAXException { 346 /* 347 * Expat complains if the external entity isn't wrapped with a root 348 * element so we add one and ignore it later on during parsing. 349 */ 350 351 // Try the character stream. 352 Reader reader = inputSource.getCharacterStream(); 353 if (reader != null) { 354 try { 355 entityParser.append("<externalEntity>"); 356 entityParser.parseFragment(reader); 357 entityParser.append("</externalEntity>"); 358 } finally { 359 IoUtils.closeQuietly(reader); 360 } 361 return; 362 } 363 364 // Try the byte stream. 365 InputStream in = inputSource.getByteStream(); 366 if (in != null) { 367 try { 368 entityParser.append("<externalEntity>" 369 .getBytes(entityParser.encoding)); 370 entityParser.parseFragment(in); 371 entityParser.append("</externalEntity>" 372 .getBytes(entityParser.encoding)); 373 } finally { 374 IoUtils.closeQuietly(in); 375 } 376 return; 377 } 378 379 // Make sure we use the user-provided systemId. 380 String systemId = inputSource.getSystemId(); 381 if (systemId == null) { 382 // TODO: We could just try our systemId here. 383 throw new ParseException("No input specified.", locator); 384 } 385 386 // Try the system id. 387 in = openUrl(systemId); 388 try { 389 entityParser.append("<externalEntity>" 390 .getBytes(entityParser.encoding)); 391 entityParser.parseFragment(in); 392 entityParser.append("</externalEntity>" 393 .getBytes(entityParser.encoding)); 394 } finally { 395 IoUtils.closeQuietly(in); 396 } 397 } 398 399 /** 400 * Creates a native entity parser. 401 * 402 * @param parentPointer pointer to parent Expat parser 403 * @param context passed to {@link #handleExternalEntity} 404 * @return pointer to native parser 405 */ createEntityParser(long parentPointer, String context)406 private static native long createEntityParser(long parentPointer, String context); 407 408 /** 409 * Appends part of an XML document. This parser will parse the given XML to 410 * the extent possible and dispatch to the appropriate methods. 411 * 412 * @param xml a whole or partial snippet of XML 413 * @throws SAXException if an error occurs during parsing 414 */ append(String xml)415 /*package*/ void append(String xml) throws SAXException { 416 try { 417 appendString(this.pointer, xml, false); 418 } catch (ExpatException e) { 419 throw new ParseException(e.getMessage(), this.locator); 420 } 421 } 422 appendString(long pointer, String xml, boolean isFinal)423 private native void appendString(long pointer, String xml, boolean isFinal) 424 throws SAXException, ExpatException; 425 426 /** 427 * Appends part of an XML document. This parser will parse the given XML to 428 * the extent possible and dispatch to the appropriate methods. 429 * 430 * @param xml a whole or partial snippet of XML 431 * @param offset into the char[] 432 * @param length of characters to use 433 * @throws SAXException if an error occurs during parsing 434 */ 435 @UnsupportedAppUsage append(char[] xml, int offset, int length)436 /*package*/ void append(char[] xml, int offset, int length) 437 throws SAXException { 438 try { 439 appendChars(this.pointer, xml, offset, length); 440 } catch (ExpatException e) { 441 throw new ParseException(e.getMessage(), this.locator); 442 } 443 } 444 appendChars(long pointer, char[] xml, int offset, int length)445 private native void appendChars(long pointer, char[] xml, int offset, 446 int length) throws SAXException, ExpatException; 447 448 /** 449 * Appends part of an XML document. This parser will parse the given XML to 450 * the extent possible and dispatch to the appropriate methods. 451 * 452 * @param xml a whole or partial snippet of XML 453 * @throws SAXException if an error occurs during parsing 454 */ append(byte[] xml)455 /*package*/ void append(byte[] xml) throws SAXException { 456 append(xml, 0, xml.length); 457 } 458 459 /** 460 * Appends part of an XML document. This parser will parse the given XML to 461 * the extent possible and dispatch to the appropriate methods. 462 * 463 * @param xml a whole or partial snippet of XML 464 * @param offset into the byte[] 465 * @param length of bytes to use 466 * @throws SAXException if an error occurs during parsing 467 */ 468 @UnsupportedAppUsage append(byte[] xml, int offset, int length)469 /*package*/ void append(byte[] xml, int offset, int length) 470 throws SAXException { 471 try { 472 appendBytes(this.pointer, xml, offset, length); 473 } catch (ExpatException e) { 474 throw new ParseException(e.getMessage(), this.locator); 475 } 476 } 477 appendBytes(long pointer, byte[] xml, int offset, int length)478 private native void appendBytes(long pointer, byte[] xml, int offset, 479 int length) throws SAXException, ExpatException; 480 481 /** 482 * Parses an XML document from the given input stream. 483 */ parseDocument(InputStream in)484 /*package*/ void parseDocument(InputStream in) throws IOException, 485 SAXException { 486 startDocument(); 487 parseFragment(in); 488 finish(); 489 endDocument(); 490 } 491 492 /** 493 * Parses an XML Document from the given reader. 494 */ parseDocument(Reader in)495 /*package*/ void parseDocument(Reader in) throws IOException, SAXException { 496 startDocument(); 497 parseFragment(in); 498 finish(); 499 endDocument(); 500 } 501 502 /** 503 * Parses XML from the given Reader. 504 */ parseFragment(Reader in)505 private void parseFragment(Reader in) throws IOException, SAXException { 506 char[] buffer = new char[BUFFER_SIZE / 2]; 507 int length; 508 while ((length = in.read(buffer)) != -1) { 509 try { 510 appendChars(this.pointer, buffer, 0, length); 511 } catch (ExpatException e) { 512 throw new ParseException(e.getMessage(), locator); 513 } 514 } 515 } 516 517 /** 518 * Parses XML from the given input stream. 519 */ parseFragment(InputStream in)520 private void parseFragment(InputStream in) 521 throws IOException, SAXException { 522 byte[] buffer = new byte[BUFFER_SIZE]; 523 int length; 524 while ((length = in.read(buffer)) != -1) { 525 try { 526 appendBytes(this.pointer, buffer, 0, length); 527 } catch (ExpatException e) { 528 throw new ParseException(e.getMessage(), this.locator); 529 } 530 } 531 } 532 startDocument()533 private void startDocument() throws SAXException { 534 ContentHandler contentHandler = xmlReader.contentHandler; 535 if (contentHandler != null) { 536 contentHandler.setDocumentLocator(this.locator); 537 contentHandler.startDocument(); 538 } 539 } 540 endDocument()541 private void endDocument() throws SAXException { 542 ContentHandler contentHandler; 543 contentHandler = xmlReader.contentHandler; 544 if (contentHandler != null) { 545 contentHandler.endDocument(); 546 } 547 } 548 549 /** 550 * Indicate that we're finished parsing. 551 * 552 * @throws SAXException if the xml is incomplete 553 */ 554 @UnsupportedAppUsage finish()555 /*package*/ void finish() throws SAXException { 556 try { 557 appendString(this.pointer, "", true); 558 } catch (ExpatException e) { 559 throw new ParseException(e.getMessage(), this.locator); 560 } 561 } 562 finalize()563 @Override protected synchronized void finalize() throws Throwable { 564 try { 565 if (this.pointer != 0) { 566 release(this.pointer); 567 this.pointer = 0; 568 } 569 } finally { 570 super.finalize(); 571 } 572 } 573 574 /** 575 * Releases all native objects. 576 */ release(long pointer)577 private native void release(long pointer); 578 579 /** 580 * Releases native parser only. 581 */ releaseParser(long pointer)582 private static native void releaseParser(long pointer); 583 584 /** 585 * Initialize static resources. 586 */ staticInitialize(String emptyString)587 private static native void staticInitialize(String emptyString); 588 589 static { 590 staticInitialize(""); 591 } 592 593 /** 594 * Gets the current line number within the XML file. 595 */ line()596 private int line() { 597 return line(this.pointer); 598 } 599 line(long pointer)600 private static native int line(long pointer); 601 602 /** 603 * Gets the current column number within the XML file. 604 */ column()605 private int column() { 606 return column(this.pointer); 607 } 608 column(long pointer)609 private static native int column(long pointer); 610 611 /** 612 * Clones the current attributes so they can be used outside of 613 * startElement(). 614 */ 615 @UnsupportedAppUsage cloneAttributes()616 /*package*/ Attributes cloneAttributes() { 617 if (!inStartElement) { 618 throw new IllegalStateException(OUTSIDE_START_ELEMENT); 619 } 620 621 if (attributeCount == 0) { 622 return ClonedAttributes.EMPTY; 623 } 624 625 long clonePointer 626 = cloneAttributes(this.attributePointer, this.attributeCount); 627 return new ClonedAttributes(pointer, clonePointer, attributeCount); 628 } 629 cloneAttributes(long pointer, int attributeCount)630 private static native long cloneAttributes(long pointer, int attributeCount); 631 632 /** 633 * Used for cloned attributes. 634 */ 635 private static class ClonedAttributes extends ExpatAttributes { 636 // TODO: Can we please remove this? It appears unused, and the finalizer 637 // asynchronously invalidates the result returned by getPointer() at a 638 // largely unpredictable time. b/70989581 639 640 private static final Attributes EMPTY = new ClonedAttributes(0, 0, 0); 641 642 private final long parserPointer; 643 private long pointer; 644 private final int length; 645 646 /** 647 * Constructs a Java wrapper for native attributes. 648 * 649 * @param parserPointer pointer to the parse, can be 0 if length is 0. 650 * @param pointer pointer to the attributes array, can be 0 if the 651 * length is 0. 652 * @param length number of attributes 653 */ ClonedAttributes(long parserPointer, long pointer, int length)654 private ClonedAttributes(long parserPointer, long pointer, int length) { 655 this.parserPointer = parserPointer; 656 this.pointer = pointer; 657 this.length = length; 658 } 659 660 @Override getParserPointer()661 public long getParserPointer() { 662 return this.parserPointer; 663 } 664 665 @Override getPointer()666 public long getPointer() { 667 return pointer; 668 } 669 670 @Override getLength()671 public int getLength() { 672 return length; 673 } 674 finalize()675 @Override protected synchronized void finalize() throws Throwable { 676 try { 677 if (pointer != 0) { 678 freeAttributes(pointer); 679 pointer = 0; 680 } 681 } finally { 682 super.finalize(); 683 } 684 } 685 } 686 687 private class ExpatLocator implements Locator { 688 getPublicId()689 public String getPublicId() { 690 return publicId; 691 } 692 getSystemId()693 public String getSystemId() { 694 return systemId; 695 } 696 getLineNumber()697 public int getLineNumber() { 698 return line(); 699 } 700 getColumnNumber()701 public int getColumnNumber() { 702 return column(); 703 } 704 705 @Override toString()706 public String toString() { 707 return "Locator[publicId: " + publicId + ", systemId: " + systemId 708 + ", line: " + getLineNumber() 709 + ", column: " + getColumnNumber() + "]"; 710 } 711 } 712 713 /** 714 * Attributes that are only valid during startElement(). 715 */ 716 private class CurrentAttributes extends ExpatAttributes { 717 718 @Override getParserPointer()719 public long getParserPointer() { 720 return pointer; 721 } 722 723 @Override getPointer()724 public long getPointer() { 725 if (!inStartElement) { 726 throw new IllegalStateException(OUTSIDE_START_ELEMENT); 727 } 728 return attributePointer; 729 } 730 731 @Override getLength()732 public int getLength() { 733 if (!inStartElement) { 734 throw new IllegalStateException(OUTSIDE_START_ELEMENT); 735 } 736 return attributeCount; 737 } 738 } 739 740 /** 741 * Includes line and column in the message. 742 */ 743 private static class ParseException extends SAXParseException { 744 ParseException(String message, Locator locator)745 private ParseException(String message, Locator locator) { 746 super(makeMessage(message, locator), locator); 747 } 748 makeMessage(String message, Locator locator)749 private static String makeMessage(String message, Locator locator) { 750 return makeMessage(message, locator.getLineNumber(), 751 locator.getColumnNumber()); 752 } 753 makeMessage( String message, int line, int column)754 private static String makeMessage( 755 String message, int line, int column) { 756 return "At line " + line + ", column " 757 + column + ": " + message; 758 } 759 } 760 761 /** 762 * Opens an InputStream for the given URL. 763 */ openUrl(String url)764 /*package*/ static InputStream openUrl(String url) throws IOException { 765 try { 766 URLConnection urlConnection = new URL(url).openConnection(); 767 urlConnection.setConnectTimeout(TIMEOUT); 768 urlConnection.setReadTimeout(TIMEOUT); 769 urlConnection.setDoInput(true); 770 urlConnection.setDoOutput(false); 771 return urlConnection.getInputStream(); 772 } catch (Exception e) { 773 IOException ioe = new IOException("Couldn't open " + url); 774 ioe.initCause(e); 775 throw ioe; 776 } 777 } 778 779 /** 780 * Parses an external entity. 781 */ 782 private static class EntityParser extends ExpatParser { 783 784 @UnsupportedAppUsage 785 private int depth = 0; 786 EntityParser(String encoding, ExpatReader xmlReader, long pointer, String publicId, String systemId)787 private EntityParser(String encoding, ExpatReader xmlReader, 788 long pointer, String publicId, String systemId) { 789 super(encoding, xmlReader, pointer, publicId, systemId); 790 } 791 792 @Override startElement(String uri, String localName, String qName, long attributePointer, int attributeCount)793 void startElement(String uri, String localName, String qName, 794 long attributePointer, int attributeCount) throws SAXException { 795 /* 796 * Skip topmost element generated by our workaround in 797 * {@link #handleExternalEntity}. 798 */ 799 if (depth++ > 0) { 800 super.startElement(uri, localName, qName, attributePointer, 801 attributeCount); 802 } 803 } 804 805 @Override endElement(String uri, String localName, String qName)806 void endElement(String uri, String localName, String qName) 807 throws SAXException { 808 if (--depth > 0) { 809 super.endElement(uri, localName, qName); 810 } 811 } 812 813 @Override 814 @SuppressWarnings("FinalizeDoesntCallSuperFinalize") finalize()815 protected synchronized void finalize() throws Throwable { 816 /* 817 * Don't release our native resources. We do so explicitly in 818 * {@link #handleExternalEntity} and we don't want to release the 819 * parsing context--our parent is using it. 820 */ 821 } 822 } 823 } 824