Clover coverage report - Clover results for XOM 1.2d1
Coverage timestamp: Wed Feb 8 2006 08:31:33 EST
file stats: LOC: 1,378   Methods: 35
NCLOC: 1,101   Classes: 3
 
 Source file Conditionals Statements Methods TOTAL
Verifier.java 97.6% 97.5% 97.1% 97.5%
coverage coverage
 1    /* Copyright 2002-2005 Elliotte Rusty Harold
 2   
 3    This library is free software; you can redistribute it and/or modify
 4    it under the terms of version 2.1 of the GNU Lesser General Public
 5    License as published by the Free Software Foundation.
 6   
 7    This library is distributed in the hope that it will be useful,
 8    but WITHOUT ANY WARRANTY; without even the implied warranty of
 9    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 10    GNU Lesser General Public License for more details.
 11   
 12    You should have received a copy of the GNU Lesser General Public
 13    License along with this library; if not, write to the
 14    Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 15    Boston, MA 02111-1307 USA
 16   
 17    You can contact Elliotte Rusty Harold by sending e-mail to
 18    elharo@metalab.unc.edu. Please include the word "XOM" in the
 19    subject line. The XOM home page is located at http://www.xom.nu/
 20    */
 21   
 22    package nu.xom;
 23   
 24    import java.io.DataInputStream;
 25    import java.io.IOException;
 26    import java.io.InputStream;
 27    import java.io.Reader;
 28    import java.io.StringReader;
 29    import java.util.StringTokenizer;
 30   
 31    import org.xml.sax.EntityResolver;
 32    import org.xml.sax.InputSource;
 33    import org.xml.sax.SAXException;
 34    import org.xml.sax.XMLReader;
 35   
 36    /**
 37    * <p>
 38    * <code>Verifier</code> checks names and data for
 39    * compliance with XML 1.0 and Namespaces in XML rules.
 40    * </p>
 41    *
 42    * @author Elliotte Rusty Harold
 43    * @version 1.1b4
 44    *
 45    */
 46    final class Verifier {
 47   
 48  0 private Verifier() {}
 49   
 50    // constants for the bit flags in the characters lookup table
 51    private final static byte XML_CHARACTER = 1;
 52    private final static byte NAME_CHARACTER = 2;
 53    private final static byte NAME_START_CHARACTER = 4;
 54    private final static byte NCNAME_CHARACTER = 8;
 55   
 56    private static byte[] flags = null;
 57   
 58    static {
 59   
 60  33 ClassLoader loader = Verifier.class.getClassLoader();
 61  33 if (loader != null) loadFlags(loader);
 62    // If that didn't work, try a different ClassLoader
 63  33 if (flags == null) {
 64  0 loader = Thread.currentThread().getContextClassLoader();
 65  0 loadFlags(loader);
 66    }
 67   
 68    }
 69   
 70   
 71  33 private static void loadFlags(ClassLoader loader) {
 72   
 73  33 DataInputStream in = null;
 74  33 try {
 75  33 InputStream raw = loader.getResourceAsStream("nu/xom/characters.dat");
 76  33 if (raw == null) {
 77  0 throw new RuntimeException("Broken XOM installation: "
 78    + "could not load nu/xom/characters.dat");
 79    }
 80    // buffer this????
 81  33 in = new DataInputStream(raw);
 82  33 flags = new byte[65536];
 83  33 in.readFully(flags);
 84    }
 85    catch (IOException ex) {
 86  0 throw new RuntimeException("Broken XOM installation: "
 87    + "could not load nu/xom/characters.dat");
 88    }
 89    finally {
 90  33 try {
 91  33 if (in != null) in.close();
 92    }
 93    catch (IOException ex) {
 94    // no big deal
 95    }
 96    }
 97   
 98    }
 99   
 100   
 101    /**
 102    * <p>
 103    * Check whether <code>name</code> is
 104    * a non-colonized name as defined in
 105    * <cite>Namespaces in XML</cite>.
 106    * </p>
 107    *
 108    * @param name <code>String</code> name to check
 109    *
 110    * @throws IllegalNameException if <code>name</code> is not a
 111    * non-colonized name
 112    */
 113  4074618 static void checkNCName(String name) {
 114   
 115  4074618 if (name == null) {
 116  2 throwIllegalNameException(name, "NCNames cannot be null");
 117    }
 118   
 119  4074616 int length = name.length();
 120  4074616 if (length == 0) {
 121  5 throwIllegalNameException(name, "NCNames cannot be empty");
 122    }
 123   
 124  4074611 char first = name.charAt(0);
 125  4074611 if ((flags[first] & NAME_START_CHARACTER) == 0) {
 126  61447 throwIllegalNameException(name, "NCNames cannot start " +
 127    "with the character " + Integer.toHexString(first));
 128    }
 129   
 130  4013164 for (int i = 1; i < length; i++) {
 131  907344 char c = name.charAt(i);
 132  907344 if ((flags[c] & NCNAME_CHARACTER) == 0) {
 133  71 if (c == ':') {
 134  10 throwIllegalNameException(name, "NCNames cannot contain colons");
 135    }
 136    else {
 137  61 throwIllegalNameException(name, "0x"
 138    + Integer.toHexString(c) + " is not a legal NCName character");
 139    }
 140    }
 141    }
 142   
 143    }
 144   
 145   
 146  61531 private static void throwIllegalNameException(String name, String message) {
 147  61531 IllegalNameException ex = new IllegalNameException(message);
 148  61531 ex.setData(name);
 149  61531 throw ex;
 150    }
 151   
 152   
 153  41 private static void throwIllegalCharacterDataException(String data, String message) {
 154  41 IllegalDataException ex = new IllegalCharacterDataException(message);
 155  41 ex.setData(data);
 156  41 throw ex;
 157    }
 158   
 159   
 160  1329 private static void throwMalformedURIException(String uri, String message) {
 161  1329 MalformedURIException ex = new MalformedURIException(message);
 162  1329 ex.setData(uri);
 163  1329 throw ex;
 164    }
 165   
 166   
 167    /**
 168    * <p>
 169    * This methods checks whether a string contains only
 170    * characters allowed by the XML 1.0 specification.
 171    * </p>
 172    *
 173    * @param text <code>String</code> value to verify
 174    *
 175    * @throws IllegalCharacterDataException if <code>text</code> is
 176    * not legal PCDATA
 177    */
 178  4807408 static void checkPCDATA(String text) {
 179   
 180  1 if (text == null) throwIllegalCharacterDataException(text, "Null text");
 181   
 182  4807407 char[] data = text.toCharArray();
 183  4807407 for (int i = 0, len = data.length; i < len; i++) {
 184  12911183 int result = data[i];
 185  12911183 if (result >= 0xD800 && result <= 0xDBFF) {
 186  926271 try {
 187  926271 int low = data[i+1];
 188  926270 if (low < 0xDC00 || low > 0xDFFF) {
 189  12 IllegalCharacterDataException ex
 190    = new IllegalCharacterDataException("Bad surrogate pair");
 191  12 ex.setData(text);
 192  12 throw ex;
 193    }
 194  926258 i++; // increment past low surrogate
 195    }
 196    catch (ArrayIndexOutOfBoundsException ex) {
 197  1 IllegalCharacterDataException ide
 198    = new IllegalCharacterDataException("Bad Surrogate Pair", ex);
 199  1 ide.setData(text);
 200  1 throw ide;
 201    }
 202    // all properly matched surrogate pairs are legal in PCDATA
 203    } // end if
 204  11984912 else if ((flags[result] & XML_CHARACTER) == 0) {
 205  40 throwIllegalCharacterDataException(text, "0x"
 206    + Integer.toHexString(result)
 207    + " is not allowed in XML content");
 208    }
 209   
 210    }
 211   
 212    }
 213   
 214   
 215    /**
 216    * <p>
 217    * Checks a string to see if it is a syntactically correct
 218    * RFC 3986 URI reference. Both absolute and relative
 219    * URIs are supported, as are URIs with fragment identifiers.
 220    * </p>
 221    *
 222    * @param uri <code>String</code> containing the potential URI
 223    *
 224    * @throws MalformedURIException if this is not a
 225    * legal URI reference
 226    */
 227  3313 static void checkURIReference(String uri) {
 228   
 229  2 if ((uri == null) || uri.length() == 0) return;
 230   
 231  3311 URIUtil.ParsedURI parsed = new URIUtil.ParsedURI(uri);
 232  3311 try {
 233  1255 if (parsed.scheme != null) checkScheme(parsed.scheme);
 234  1231 if (parsed.authority != null) checkAuthority(parsed.authority);
 235  3290 checkPath(parsed.path);
 236  11 if (parsed.fragment != null) checkFragment(parsed.fragment);
 237  15 if (parsed.query != null) checkQuery(parsed.query);
 238    }
 239    catch (MalformedURIException ex) {
 240  57 ex.setData(uri);
 241  57 throw ex;
 242    }
 243   
 244    }
 245   
 246   
 247  1086 private static void checkQuery(final String query) {
 248   
 249  1086 int length = query.length();
 250  1086 for (int i = 0; i < length; i++) {
 251  7960 char c = query.charAt(i);
 252  7960 if (c == '%') {
 253  9 try {
 254  9 if (!isHexDigit(query.charAt(i+1)) || !isHexDigit(query.charAt(i+2))) {
 255  2 throwMalformedURIException(query,
 256    "Bad percent escape sequence");
 257    }
 258    }
 259    catch (StringIndexOutOfBoundsException ex) {
 260  2 throwMalformedURIException(query,
 261    "Bad percent escape sequence");
 262    }
 263  5 i += 2;
 264    }
 265  7951 else if (!isQueryCharacter(c)) {
 266  943 throw new MalformedURIException(
 267    "Illegal query character " + c
 268    );
 269    }
 270    }
 271   
 272    }
 273   
 274   
 275    // same for fragment ID
 276  7951 private static boolean isQueryCharacter(char c) {
 277   
 278  7951 switch(c) {
 279  1 case '!': return true;
 280  1 case '"': return false;
 281  1 case '#': return false;
 282  1 case '$': return true;
 283  0 case '%': return false; // tested in checkQuery
 284  8 case '&': return true;
 285  1 case '\'': return true;
 286  1 case '(': return true;
 287  1 case ')': return true;
 288  1 case '*': return true;
 289  1 case '+': return true;
 290  1 case ',': return true;
 291  3 case '-': return true;
 292  16 case '.': return true;
 293  16 case '/': return true;
 294  7 case '0': return true;
 295  15 case '1': return true;
 296  3 case '2': return true;
 297  5 case '3': return true;
 298  1 case '4': return true;
 299  1 case '5': return true;
 300  1 case '6': return true;
 301  3 case '7': return true;
 302  1 case '8': return true;
 303  5 case '9': return true;
 304  6 case ':': return true;
 305  11 case ';': return true;
 306  1 case '<': return false;
 307  1057 case '=': return true;
 308  1 case '>': return false;
 309  8 case '?': return true;
 310  5 case '@': return true;
 311  3 case 'A': return true;
 312  1 case 'B': return true;
 313  1 case 'C': return true;
 314  13 case 'D': return true;
 315  3 case 'E': return true;
 316  7 case 'F': return true;
 317  1 case 'G': return true;
 318  3 case 'H': return true;
 319  11 case 'I': return true;
 320  1 case 'J': return true;
 321  1 case 'K': return true;
 322  1 case 'L': return true;
 323  12 case 'M': return true;
 324  6 case 'N': return true;
 325  6 case 'O': return true;
 326  1 case 'P': return true;
 327  1 case 'Q': return true;
 328  1 case 'R': return true;
 329  6 case 'S': return true;
 330  1 case 'T': return true;
 331  7 case 'U': return true;
 332  1 case 'V': return true;
 333  1 case 'W': return true;
 334  6 case 'X': return true;
 335  1 case 'Y': return true;
 336  1 case 'Z': return true;
 337  1 case '[': return false;
 338  1 case '\\': return false;
 339  1 case ']': return false;
 340  2 case '^': return false;
 341  35 case '_': return true;
 342  1 case '`': return false;
 343  1098 case 'a': return true;
 344  10 case 'b': return true;
 345  30 case 'c': return true;
 346  21 case 'd': return true;
 347  1120 case 'e': return true;
 348  12 case 'f': return true;
 349  14 case 'g': return true;
 350  34 case 'h': return true;
 351  12 case 'i': return true;
 352  12 case 'j': return true;
 353  5 case 'k': return true;
 354  953 case 'l': return true;
 355  138 case 'm': return true;
 356  136 case 'n': return true;
 357  29 case 'o': return true;
 358  17 case 'p': return true;
 359  3 case 'q': return true;
 360  40 case 'r': return true;
 361  51 case 's': return true;
 362  50 case 't': return true;
 363  945 case 'u': return true;
 364  936 case 'v': return true;
 365  8 case 'w': return true;
 366  7 case 'x': return true;
 367  13 case 'y': return true;
 368  1 case 'z': return true;
 369  1 case '{': return false;
 370  1 case '|': return false;
 371  1 case '}': return false;
 372  1 case '~': return true;
 373    }
 374  930 return false;
 375   
 376    }
 377   
 378   
 379  28 private static void checkFragment(String fragment) {
 380    // The BNF for fragments is the same as for query strings
 381  28 checkQuery(fragment);
 382    }
 383   
 384   
 385    // Besides the legal characters issues, a path must
 386    // not contain two consecutive forward slashes
 387  59848 private static void checkPath(final String path) {
 388   
 389  59848 int length = path.length();
 390  59848 char[] text = path.toCharArray();
 391  59848 for (int i = 0; i < length; i++) {
 392  2988270 char c = text[i];
 393  2988270 if (c == '/') {
 394  386014 if (i < length-1) {
 395  383843 if (text[i+1] == '/') {
 396  207 throwMalformedURIException(path,
 397    "Double slash (//) in path");
 398    }
 399    }
 400    }
 401  2602256 else if (c == '%') {
 402  319 try {
 403  319 if (!isHexDigit(text[i+1])
 404    || !isHexDigit(text[i+2])) {
 405  47 throwMalformedURIException(path,
 406    "Bad percent escape sequence");
 407    }
 408    }
 409    catch (ArrayIndexOutOfBoundsException ex) {
 410  5 throwMalformedURIException(path,
 411    "Bad percent escape sequence");
 412    }
 413  267 i += 2;
 414    }
 415  2601937 else if (!isPathCharacter(c)) {
 416  88 throwMalformedURIException(path,
 417    "Illegal path character " + c
 418    );
 419    }
 420    }
 421   
 422    }
 423   
 424   
 425  58622 private static void checkAuthority(String authority) {
 426   
 427  58622 String userInfo = null;
 428  58622 String host = null;
 429  58622 String port = null;
 430   
 431  58622 int atSign = authority.indexOf('@');
 432  58622 if (atSign != -1) {
 433  1047 userInfo = authority.substring(0, atSign);
 434  1047 authority = authority.substring(atSign+1);
 435    }
 436   
 437  58622 int colon;
 438  58622 if (authority.startsWith("[")) {
 439  41 colon = authority.indexOf("]:");
 440  1 if (colon != -1) colon = colon+1;
 441    }
 442  58581 else colon = authority.indexOf(':');
 443   
 444  58622 if (colon != -1) {
 445  19 host = authority.substring(0, colon);
 446  19 port = authority.substring(colon+1);
 447    }
 448    else {
 449  58603 host = authority;
 450    }
 451   
 452  1047 if (userInfo != null) checkUserInfo(userInfo);
 453  19 if (port != null) checkPort(port);
 454  57673 checkHost(host);
 455   
 456    }
 457   
 458   
 459  57673 private static void checkHost(final String host) {
 460   
 461  57673 int length = host.length();
 462  51151 if (length == 0) return; // file URI
 463   
 464  6522 char[] text = host.toCharArray();
 465  6522 if (text[0] == '[') {
 466  41 if (text[length-1] != ']') {
 467  1 throw new MalformedURIException("Missing closing ]");
 468    }
 469    // trim [ and ] from ends of host
 470  40 checkIP6Address(host.substring(1, length-1));
 471    }
 472    else {
 473  6481 if (length > 255) {
 474  1 throw new MalformedURIException("Host name too long: " + host);
 475    }
 476   
 477  6480 for (int i = 0; i < length; i++) {
 478  66848 char c = text[i];
 479  66848 if (c == '%') {
 480  5 try {
 481  5 if (!isHexDigit(text[i+1]) || !isHexDigit(text[i+2])) {
 482  2 throwMalformedURIException(host,
 483    "Bad percent escape sequence");
 484    }
 485    }
 486    catch (ArrayIndexOutOfBoundsException ex) {
 487  1 throwMalformedURIException(host,
 488    "Bad percent escape sequence");
 489    }
 490  2 i += 2;
 491    }
 492  66843 else if (!isRegNameCharacter(c)) {
 493  911 throwMalformedURIException(host,
 494    "Illegal host character " + c
 495    );
 496    }
 497    }
 498    }
 499    }
 500   
 501   
 502  66843 private static boolean isRegNameCharacter(char c) {
 503   
 504  66843 switch(c) {
 505  1 case '!': return true;
 506  1 case '"': return false;
 507  1 case '#': return false;
 508  1 case '$': return true;
 509  0 case '%': return false; // checked separately
 510  1 case '&': return true;
 511  1 case '\'': return true;
 512  1 case '(': return true;
 513  1 case ')': return true;
 514  1 case '*': return true;
 515  1 case '+': return true;
 516  2 case ',': return true;
 517  1 case '-': return true;
 518  10688 case '.': return true;
 519  0 case '/': return false;
 520  2 case '0': return true;
 521  210 case '1': return true;
 522  84 case '2': return true;
 523  2546 case '3': return true;
 524  1 case '4': return true;
 525  1 case '5': return true;
 526  1 case '6': return true;
 527  1 case '7': return true;
 528  1 case '8': return true;
 529  1 case '9': return true;
 530  0 case ':': return false;
 531  1 case ';': return true;
 532  1 case '<': return false;
 533  1 case '=': return true;
 534  1 case '>': return false;
 535  0 case '?': return false;
 536  0 case '@': return false;
 537  7 case 'A': return true;
 538  1 case 'B': return true;
 539  7 case 'C': return true;
 540  1 case 'D': return true;
 541  14 case 'E': return true;
 542  1 case 'F': return true;
 543  1 case 'G': return true;
 544  1 case 'H': return true;
 545  1 case 'I': return true;
 546  1 case 'J': return true;
 547  1 case 'K': return true;
 548  7 case 'L': return true;
 549  13 case 'M': return true;
 550  12 case 'N': return true;
 551  7 case 'O': return true;
 552  7 case 'P': return true;
 553  1 case 'Q': return true;
 554  1 case 'R': return true;
 555  1 case 'S': return true;
 556  1 case 'T': return true;
 557  1 case 'U': return true;
 558  1 case 'V': return true;
 559  19 case 'W': return true;
 560  7 case 'X': return true;
 561  1 case 'Y': return true;
 562  1 case 'Z': return true;
 563  1 case '[': return false;
 564  1 case '\\': return false;
 565  1 case ']': return false;
 566  2 case '^': return false;
 567  1 case '_': return true;
 568  1 case '`': return false;
 569  2274 case 'a': return true;
 570  227 case 'b': return true;
 571  3582 case 'c': return true;
 572  840 case 'd': return true;
 573  4886 case 'e': return true;
 574  175 case 'f': return true;
 575  2905 case 'g': return true;
 576  131 case 'h': return true;
 577  150 case 'i': return true;
 578  72 case 'j': return true;
 579  2 case 'k': return true;
 580  1886 case 'l': return true;
 581  4314 case 'm': return true;
 582  639 case 'n': return true;
 583  5471 case 'o': return true;
 584  1794 case 'p': return true;
 585  1 case 'q': return true;
 586  2963 case 'r': return true;
 587  688 case 's': return true;
 588  190 case 't': return true;
 589  467 case 'u': return true;
 590  800 case 'v': return true;
 591  15517 case 'w': return true;
 592  2133 case 'x': return true;
 593  150 case 'y': return true;
 594  8 case 'z': return true;
 595  1 case '{': return false;
 596  1 case '|': return false;
 597  1 case '}': return false;
 598  1 case '~': return true;
 599    }
 600  898 return false;
 601   
 602    }
 603   
 604   
 605  19 private static void checkPort(String port) {
 606   
 607  19 for (int i = port.length()-1; i >= 0; i--) {
 608  51 char c = port.charAt(i);
 609  51 if (c < '0' || c > '9') {
 610  2 throw new MalformedURIException("Bad port: " + port);
 611    }
 612    }
 613   
 614    }
 615   
 616   
 617  1047 private static void checkUserInfo(String userInfo) {
 618   
 619  1047 int length = userInfo.length();
 620  1047 for (int i = 0; i < length; i++) {
 621  1186 char c = userInfo.charAt(i);
 622  1186 if (c == '%') {
 623  5 try {
 624  5 if (!isHexDigit(userInfo.charAt(i+1))
 625    || !isHexDigit(userInfo.charAt(i+2))) {
 626  1 throwMalformedURIException(userInfo,
 627    "Bad percent escape sequence");
 628    }
 629    }
 630    catch (StringIndexOutOfBoundsException ex) {
 631  2 throwMalformedURIException(userInfo,
 632    "Bad percent escape sequence");
 633    }
 634  2 i += 2;
 635    }
 636  1181 else if (!isUserInfoCharacter(c)) {
 637  944 throw new MalformedURIException("Bad user info: " + userInfo);
 638    }
 639    }
 640   
 641    }
 642   
 643   
 644  59712 private static void checkScheme(String scheme) {
 645   
 646    // http is probably 99% of cases so check it first
 647  7407 if ("http".equals(scheme)) return;
 648   
 649  52305 if (scheme.length() == 0) {
 650  1 throw new MalformedURIException("URIs cannot begin with a colon");
 651    }
 652  52304 char c = scheme.charAt(0);
 653  52304 if (!isAlpha(c)) {
 654  8 throw new MalformedURIException(
 655    "Illegal initial scheme character " + c);
 656    }
 657   
 658  52296 for (int i = scheme.length()-1; i >= 1; i--) {
 659  156894 c = scheme.charAt(i);
 660  156894 if (!isSchemeCharacter(c)) {
 661  27 throw new MalformedURIException(
 662    "Illegal scheme character " + c
 663    );
 664    }
 665    }
 666   
 667    }
 668   
 669   
 670  40 private static void checkIP6Address(String ip6Address) {
 671   
 672  40 StringTokenizer st = new StringTokenizer(ip6Address, ":", true);
 673  40 int numTokens = st.countTokens();
 674  40 if (numTokens > 15 || numTokens < 2) {
 675  1 throw new MalformedURIException(
 676    "Illegal IP6 host address: " + ip6Address
 677    );
 678    }
 679  39 for (int i = 0; i < numTokens; i++) {
 680  351 String hexPart = st.nextToken();
 681  174 if (":".equals(hexPart)) continue;
 682  177 try {
 683  177 int part = Integer.parseInt(hexPart, 16);
 684  157 if (part < 0) {
 685  2 throw new MalformedURIException(
 686    "Illegal IP6 host address: " + ip6Address
 687    );
 688    }
 689    }
 690    catch (NumberFormatException ex) {
 691  20 if (i == numTokens-1) {
 692  13 checkIP4Address(hexPart, ip6Address);
 693    }
 694    else {
 695  7 throwMalformedURIException(ip6Address,
 696    "Illegal IP6 host address: " + ip6Address
 697    );
 698    }
 699    }
 700    }
 701   
 702  23 if (ip6Address.indexOf("::") != ip6Address.lastIndexOf("::")) {
 703  2 throw new MalformedURIException(
 704    "Illegal IP6 host address: " + ip6Address
 705    );
 706    }
 707   
 708    }
 709   
 710   
 711  13 private static void checkIP4Address(String address, String ip6Address) {
 712   
 713  13 StringTokenizer st = new StringTokenizer(address, ".");
 714  13 int numTokens = st.countTokens();
 715  13 if (numTokens != 4) {
 716  3 throw new MalformedURIException(
 717    "Illegal IP6 host address: " + ip6Address
 718    );
 719    }
 720  10 for (int i = 0; i < 4; i++) {
 721  37 String decPart = st.nextToken();
 722  37 try {
 723  37 int dec = Integer.parseInt(decPart);
 724  35 if (dec > 255 || dec < 0) {
 725  2 throw new MalformedURIException(
 726    "Illegal IP6 host address: " + ip6Address
 727    );
 728    }
 729    }
 730    catch (NumberFormatException ex) {
 731  2 throw new MalformedURIException(
 732    "Illegal IP6 host address: " + ip6Address
 733    );
 734    }
 735    }
 736   
 737    }
 738   
 739   
 740  1276 static void checkXMLName(String name) {
 741   
 742  1276 if (name == null) {
 743  1 throwIllegalNameException(name, "XML names cannot be null");
 744    }
 745   
 746  1275 int length = name.length();
 747  1275 if (length == 0) {
 748  1 throwIllegalNameException(name, "XML names cannot be empty");
 749    }
 750   
 751  1274 char first = name.charAt(0);
 752  1274 if ((flags[first] & NAME_START_CHARACTER) == 0) {
 753  3 throwIllegalNameException(name, "XML names cannot start " +
 754    "with the character " + Integer.toHexString(first));
 755    }
 756   
 757  1271 for (int i = 1; i < length; i++) {
 758  6068 char c = name.charAt(i);
 759  6068 if ((flags[c] & NAME_CHARACTER) == 0) {
 760  1 throwIllegalNameException(name, "0x"
 761    + Integer.toHexString(c)
 762    + " is not a legal name character");
 763    }
 764    }
 765   
 766    }
 767   
 768   
 769    private static boolean[] C0Table = new boolean[0x21];
 770    static {
 771  33 C0Table['\n'] = true;
 772  33 C0Table['\r'] = true;
 773  33 C0Table['\t'] = true;
 774  33 C0Table[' '] = true;
 775    }
 776   
 777   
 778  2080 static boolean isXMLSpaceCharacter(char c) {
 779  2019 if (c > ' ') return false;
 780  61 return C0Table[c];
 781    }
 782   
 783   
 784  654 private static boolean isHexDigit(char c) {
 785   
 786  654 switch(c) {
 787  35 case '0': return true;
 788  47 case '1': return true;
 789  75 case '2': return true;
 790  61 case '3': return true;
 791  47 case '4': return true;
 792  67 case '5': return true;
 793  47 case '6': return true;
 794  55 case '7': return true;
 795  14 case '8': return true;
 796  20 case '9': return true;
 797  1 case ':': return false;
 798  1 case ';': return false;
 799  1 case '<': return false;
 800  1 case '=': return false;
 801  1 case '>': return false;
 802  1 case '?': return false;
 803  1 case '@': return false;
 804  13 case 'A': return true;
 805  16 case 'B': return true;
 806  27 case 'C': return true;
 807  13 case 'D': return true;
 808  14 case 'E': return true;
 809  7 case 'F': return true;
 810  2 case 'G': return false;
 811  1 case 'H': return false;
 812  1 case 'I': return false;
 813  1 case 'J': return false;
 814  1 case 'K': return false;
 815  1 case 'L': return false;
 816  1 case 'M': return false;
 817  1 case 'N': return false;
 818  1 case 'O': return false;
 819  1 case 'P': return false;
 820  1 case 'Q': return false;
 821  1 case 'R': return false;
 822  1 case 'S': return false;
 823  4 case 'T': return false;
 824  1 case 'U': return false;
 825  1 case 'V': return false;
 826  1 case 'W': return false;
 827  1 case 'X': return false;
 828  1 case 'Y': return false;
 829  1 case 'Z': return false;
 830  1 case '[': return false;
 831  1 case '\\': return false;
 832  1 case ']': return false;
 833  1 case '^': return false;
 834  1 case '_': return false;
 835  1 case '`': return false;
 836  8 case 'a': return true;
 837  6 case 'b': return true;
 838  10 case 'c': return true;
 839  6 case 'd': return true;
 840  9 case 'e': return true;
 841  5 case 'f': return true;
 842    }
 843  15 return false;
 844    }
 845   
 846   
 847    // Since namespace URIs are commonly repeated, we can save a lot
 848    // of redundant code by storing the ones we've seen before.
 849    private static URICache cache = new URICache();
 850   
 851    private final static class URICache {
 852   
 853    private final static int LOAD = 6;
 854    private String[] cache = new String[LOAD];
 855    private int position = 0;
 856   
 857  42911 synchronized boolean contains(String s) {
 858   
 859  42911 for (int i = 0; i < LOAD; i++) {
 860    // Here I'm assuming the namespace URIs are interned.
 861    // This is commonly but not always true. This won't
 862    // break if they haven't been. Using equals() instead
 863    // of == is faster when the namespace URIs haven't been
 864    // interned but slower if they have.
 865  140982 if (s == cache[i]) {
 866  37214 return true;
 867    }
 868    }
 869  5697 return false;
 870   
 871    }
 872   
 873  2498 synchronized void put(String s) {
 874  2498 cache[position] = s;
 875  2498 position++;
 876  409 if (position == LOAD) position = 0;
 877    }
 878   
 879    }
 880   
 881   
 882    /**
 883    * <p>
 884    * Checks a string to see if it is an RFC 3986 absolute
 885    * URI reference. URI references can contain fragment identifiers.
 886    * Absolute URI references must have a scheme.
 887    * </p>
 888    *
 889    * @param uri <code>String</code> to check
 890    *
 891    * @throws MalformedURIException if this is not a legal
 892    * URI reference
 893    */
 894  42911 static void checkAbsoluteURIReference(String uri) {
 895   
 896  42911 if (cache.contains(uri)) {
 897  37214 return;
 898    }
 899  5697 URIUtil.ParsedURI parsed = new URIUtil.ParsedURI(uri);
 900  5695 try {
 901  5695 if (parsed.scheme == null) {
 902  52 throwMalformedURIException(
 903    uri, "Missing scheme in absolute URI reference");
 904    }
 905  5643 checkScheme(parsed.scheme);
 906  5035 if (parsed.authority != null) checkAuthority(parsed.authority);
 907  3746 checkPath(parsed.path);
 908  17 if (parsed.fragment != null) checkFragment(parsed.fragment);
 909  1038 if (parsed.query != null) checkQuery(parsed.query);
 910  2498 cache.put(uri);
 911    }
 912    catch (MalformedURIException ex) {
 913  3197 ex.setData(uri);
 914  3197 throw ex;
 915    }
 916   
 917    }
 918   
 919   
 920  52326 static boolean isAlpha(char c) {
 921   
 922  52326 switch(c) {
 923  1 case 'A': return true;
 924  1 case 'B': return true;
 925  4 case 'C': return true;
 926  1 case 'D': return true;
 927  1 case 'E': return true;
 928  7 case 'F': return true;
 929  4 case 'G': return true;
 930  10 case 'H': return true;
 931  1 case 'I': return true;
 932  1 case 'J': return true;
 933  1 case 'K': return true;
 934  1 case 'L': return true;
 935  7 case 'M': return true;
 936  4 case 'N': return true;
 937  4 case 'O': return true;
 938  1 case 'P': return true;
 939  1 case 'Q': return true;
 940  1 case 'R': return true;
 941  1 case 'S': return true;
 942  4 case 'T': return true;
 943  4 case 'U': return true;
 944  3 case 'V': return true;
 945  3 case 'W': return true;
 946  3 case 'X': return true;
 947  1 case 'Y': return true;
 948  7 case 'Z': return true;
 949  1 case '[': return false;
 950  1 case '\\': return false;
 951  1 case ']': return false;
 952  1 case '^': return false;
 953  1 case '_': return false;
 954  1 case '`': return false;
 955  1 case 'a': return true;
 956  1 case 'b': return true;
 957  4 case 'c': return true;
 958  2 case 'd': return true;
 959  1 case 'e': return true;
 960  51621 case 'f': return true;
 961  5 case 'g': return true;
 962  4 case 'h': return true;
 963  1 case 'i': return true;
 964  1 case 'j': return true;
 965  1 case 'k': return true;
 966  1 case 'l': return true;
 967  10 case 'm': return true;
 968  4 case 'n': return true;
 969  4 case 'o': return true;
 970  2 case 'p': return true;
 971  1 case 'q': return true;
 972  1 case 'r': return true;
 973  94 case 's': return true;
 974  12 case 't': return true;
 975  447 case 'u': return true;
 976  5 case 'v': return true;
 977  3 case 'w': return true;
 978  6 case 'x': return true;
 979  1 case 'y': return true;
 980  7 case 'z': return true;
 981    }
 982   
 983  3 return false;
 984   
 985    }
 986   
 987   
 988  156894 static boolean isSchemeCharacter(char c) {
 989   
 990    /* The : and the ? cannot be reached here because they'll
 991    * have been parsed out separately before this method is
 992    * called. They're included here strictly for alignment
 993    * so the compiler will generate a table lookup.
 994    */
 995  156894 switch(c) {
 996  1 case '+': return true;
 997  2 case ',': return false;
 998  4 case '-': return true;
 999  19 case '.': return true;
 1000  1 case '/': return false;
 1001  15 case '0': return true;
 1002  1 case '1': return true;
 1003  7 case '2': return true;
 1004  23 case '3': return true;
 1005  1 case '4': return true;
 1006  13 case '5': return true;
 1007  1 case '6': return true;
 1008  3 case '7': return true;
 1009  1 case '8': return true;
 1010  13 case '9': return true;
 1011  0 case ':': return false; // unreachable
 1012  1 case ';': return false;
 1013  1 case '<': return false;
 1014  1 case '=': return false;
 1015  1 case '>': return false;
 1016  0 case '?': return false; // unreachable
 1017  1 case '@': return false;
 1018  9 case 'A': return true;
 1019  3 case 'B': return true;
 1020  5 case 'C': return true;
 1021  5 case 'D': return true;
 1022  22 case 'E': return true;
 1023  1 case 'F': return true;
 1024  1 case 'G': return true;
 1025  4 case 'H': return true;
 1026  15 case 'I': return true;
 1027  1 case 'J': return true;
 1028  5 case 'K': return true;
 1029  11 case 'L': return true;
 1030  7 case 'M': return true;
 1031  5 case 'N': return true;
 1032  10 case 'O': return true;
 1033  17 case 'P': return true;
 1034  3 case 'Q': return true;
 1035  12 case 'R': return true;
 1036  9 case 'S': return true;
 1037  20 case 'T': return true;
 1038  3 case 'U': return true;
 1039  1 case 'V': return true;
 1040  3 case 'W': return true;
 1041  3 case 'X': return true;
 1042  1 case 'Y': return true;
 1043  1 case 'Z': return true;
 1044  1 case '[': return false;
 1045  1 case '\\': return false;
 1046  1 case ']': return false;
 1047  1 case '^': return false;
 1048  1 case '_': return false;
 1049  1 case '`': return false;
 1050  12 case 'a': return true;
 1051  4 case 'b': return true;
 1052  129 case 'c': return true;
 1053  7 case 'd': return true;
 1054  51881 case 'e': return true;
 1055  3 case 'f': return true;
 1056  1 case 'g': return true;
 1057  126 case 'h': return true;
 1058  51631 case 'i': return true;
 1059  1 case 'j': return true;
 1060  5 case 'k': return true;
 1061  51624 case 'l': return true;
 1062  136 case 'm': return true;
 1063  443 case 'n': return true;
 1064  18 case 'o': return true;
 1065  18 case 'p': return true;
 1066  3 case 'q': return true;
 1067  455 case 'r': return true;
 1068  65 case 's': return true;
 1069  13 case 't': return true;
 1070  4 case 'u': return true;
 1071  1 case 'v': return true;
 1072  3 case 'w': return true;
 1073  3 case 'x': return true;
 1074  1 case 'y': return true;
 1075  1 case 'z': return true;
 1076    }
 1077   
 1078  13 return false;
 1079   
 1080    }
 1081   
 1082   
 1083  2601937 private static boolean isPathCharacter(char c) {
 1084   
 1085  2601937 switch(c) {
 1086  6 case '!': return true;
 1087  2 case '"': return false;
 1088  1 case '#': return false;
 1089  12 case '$': return true;
 1090  0 case '%': return false; // checked separately
 1091  4 case '&': return true;
 1092  8 case '\'': return true;
 1093  5 case '(': return true;
 1094  5 case ')': return true;
 1095  9 case '*': return true;
 1096  16 case '+': return true;
 1097  76 case ',': return true;
 1098  15027 case '-': return true;
 1099  55748 case '.': return true;
 1100  0 case '/': return false; // handled separately
 1101  10401 case '0': return true;
 1102  8096 case '1': return true;
 1103  5552 case '2': return true;
 1104  2853 case '3': return true;
 1105  2598 case '4': return true;
 1106  2704 case '5': return true;
 1107  2935 case '6': return true;
 1108  1364 case '7': return true;
 1109  2241 case '8': return true;
 1110  2971 case '9': return true;
 1111  210 case ':': return true;
 1112  23 case ';': return true;
 1113  2 case '<': return false;
 1114  88 case '=': return true;
 1115  1 case '>': return false;
 1116  0 case '?': return false;
 1117  21 case '@': return true;
 1118  1365 case 'A': return true;
 1119  633 case 'B': return true;
 1120  4271 case 'C': return true;
 1121  320 case 'D': return true;
 1122  5152 case 'E': return true;
 1123  3917 case 'F': return true;
 1124  4 case 'G': return true;
 1125  101 case 'H': return true;
 1126  1443 case 'I': return true;
 1127  4 case 'J': return true;
 1128  219 case 'K': return true;
 1129  416 case 'L': return true;
 1130  54476 case 'M': return true;
 1131  530 case 'N': return true;
 1132  52225 case 'O': return true;
 1133  53925 case 'P': return true;
 1134  4 case 'Q': return true;
 1135  2205 case 'R': return true;
 1136  10974 case 'S': return true;
 1137  16451 case 'T': return true;
 1138  52423 case 'U': return true;
 1139  504 case 'V': return true;
 1140  126 case 'W': return true;
 1141  53766 case 'X': return true;
 1142  10 case 'Y': return true;
 1143  4 case 'Z': return true;
 1144  1 case '[': return false;
 1145  2 case '\\': return false;
 1146  2 case ']': return false;
 1147  3 case '^': return false;
 1148  16297 case '_': return true;
 1149  2 case '`': return false;
 1150  213867 case 'a': return true;
 1151  14959 case 'b': return true;
 1152  133927 case 'c': return true;
 1153  66347 case 'd': return true;
 1154  225508 case 'e': return true;
 1155  54397 case 'f': return true;
 1156  703 case 'g': return true;
 1157  53602 case 'h': return true;
 1158  57036 case 'i': return true;
 1159  52106 case 'j': return true;
 1160  2093 case 'k': return true;
 1161  153678 case 'l': return true;
 1162  88881 case 'm': return true;
 1163  95675 case 'n': return true;
 1164  156499 case 'o': return true;
 1165  6403 case 'p': return true;
 1166  116 case 'q': return true;
 1167  164498 case 'r': return true;
 1168  286947 case 's': return true;
 1169  221907 case 't': return true;
 1170  13761 case 'u': return true;
 1171  10065 case 'v': return true;
 1172  3898 case 'w': return true;
 1173  78113 case 'x': return true;
 1174  665 case 'y': return true;
 1175  27 case 'z': return true;
 1176  2 case '{': return false;
 1177  2 case '|': return false;
 1178  2 case '}': return false;
 1179  1433 case '~': return true;
 1180    }
 1181   
 1182  66 return false;
 1183   
 1184    }
 1185   
 1186   
 1187  1181 private static boolean isUserInfoCharacter(char c) {
 1188   
 1189  1181 switch(c) {
 1190  1 case '!': return true;
 1191  1 case '"': return false;
 1192  2 case '#': return false;
 1193  1 case '$': return true;
 1194  0 case '%': return false; // checked separately
 1195  1 case '&': return true;
 1196  1 case '\'': return true;
 1197  1 case '(': return true;
 1198  1 case ')': return true;
 1199  1 case '*': return true;
 1200  1 case '+': return true;
 1201  1 case ',': return true;
 1202  1 case '-': return true;
 1203  1 case '.': return true;
 1204  0 case '/': return true;
 1205  1 case '0': return true;
 1206  1 case '1': return true;
 1207  1 case '2': return true;
 1208  1 case '3': return true;
 1209  1 case '4': return true;
 1210  1 case '5': return true;
 1211  1 case '6': return true;
 1212  1 case '7': return true;
 1213  1 case '8': return true;
 1214  1 case '9': return true;
 1215  2 case ':': return true;
 1216  2 case ';': return true;
 1217  1 case '<': return false;
 1218  1 case '=': return true;
 1219  1 case '>': return false;
 1220  0 case '?': return false;
 1221  0 case '@': return false;
 1222  1 case 'A': return true;
 1223  1 case 'B': return true;
 1224  1 case 'C': return true;
 1225  2 case 'D': return true;
 1226  2 case 'E': return true;
 1227  1 case 'F': return true;
 1228  1 case 'G': return true;
 1229  1 case 'H': return true;
 1230  1 case 'I': return true;
 1231  1 case 'J': return true;
 1232  1 case 'K': return true;
 1233  1 case 'L': return true;
 1234  1 case 'M': return true;
 1235  1 case 'N': return true;
 1236  1 case 'O': return true;
 1237  1 case 'P': return true;
 1238  1 case 'Q': return true;
 1239  1 case 'R': return true;
 1240  1 case 'S': return true;
 1241  2 case 'T': return true;
 1242  1 case 'U': return true;
 1243  1 case 'V': return true;
 1244  1 case 'W': return true;
 1245  1 case 'X': return true;
 1246  1 case 'Y': return true;
 1247  1 case 'Z': return true;
 1248  1 case '[': return false;
 1249  1 case '\\': return false;
 1250  1 case ']': return false;
 1251  2 case '^': return false;
 1252  1 case '_': return true;
 1253  1 case '`': return false;
 1254  4 case 'a': return true;
 1255  1 case 'b': return true;
 1256  42 case 'c': return true;
 1257  3 case 'd': return true;
 1258  18 case 'e': return true;
 1259  1 case 'f': return true;
 1260  1 case 'g': return true;
 1261  2 case 'h': return true;
 1262  4 case 'i': return true;
 1263  1 case 'j': return true;
 1264  1 case 'k': return true;
 1265  2 case 'l': return true;
 1266  2 case 'm': return true;
 1267  3 case 'n': return true;
 1268  3 case 'o': return true;
 1269  2 case 'p': return true;
 1270  1 case 'q': return true;
 1271  16 case 'r': return true;
 1272  18 case 's': return true;
 1273  4 case 't': return true;
 1274  15 case 'u': return true;
 1275  2 case 'v': return true;
 1276  1 case 'w': return true;
 1277  31 case 'x': return true;
 1278  1 case 'y': return true;
 1279  1 case 'z': return true;
 1280  1 case '{': return false;
 1281  1 case '|': return false;
 1282  1 case '}': return false;
 1283  1 case '~': return true;
 1284    }
 1285   
 1286  930 return false;
 1287   
 1288    }
 1289   
 1290   
 1291    /**
 1292    * Check to see that this string is an absolute URI,
 1293    * neither a relative URI nor a URI reference.
 1294    *
 1295    */
 1296  52815 static void checkAbsoluteURI(String uri) {
 1297   
 1298  52815 URIUtil.ParsedURI parsed = new URIUtil.ParsedURI(uri);
 1299  52815 try {
 1300  52815 if (parsed.scheme == null) {
 1301  1 throwMalformedURIException(uri, "Missing scheme in absolute URI");
 1302    }
 1303  52814 checkScheme(parsed.scheme);
 1304  52356 if (parsed.authority != null) checkAuthority(parsed.authority);
 1305  52812 checkPath(parsed.path);
 1306  52802 if (parsed.fragment != null) {
 1307  1 throwMalformedURIException(uri, "URIs cannot have fragment identifiers");
 1308    }
 1309  5 if (parsed.query != null) checkQuery(parsed.query);
 1310    }
 1311    catch (MalformedURIException ex) {
 1312  14 ex.setData(uri);
 1313  14 throw ex;
 1314    }
 1315   
 1316    }
 1317   
 1318   
 1319    // For use in checking internal DTD subsets
 1320    private static XMLReader parser;
 1321   
 1322  4 static synchronized void checkInternalDTDSubset(String subset) {
 1323   
 1324  4 if (parser == null) {
 1325  2 final InputSource empty = new InputSource(new EmptyReader());
 1326  2 parser = Builder.findParser(false);
 1327    // parser = new org.apache.crimson.parser.XMLReaderImpl();
 1328    // Now let's stop this parser from loading any external
 1329    // entities the subset references
 1330  2 parser.setEntityResolver(new EntityResolver() {
 1331   
 1332  1 public InputSource resolveEntity(String publicID, String systemID) {
 1333  1 return empty;
 1334    }
 1335   
 1336    });
 1337    }
 1338   
 1339  4 String doc = "<!DOCTYPE a [" + subset + "]><a/>";
 1340  4 try {
 1341  4 InputSource source = new InputSource(new StringReader(doc));
 1342    // just to make sure relative URLs can be resolved; don't
 1343    // actually need to connect to this; the EntityResolver
 1344    // prevents that
 1345  4 source.setSystemId("http://www.example.org/");
 1346  4 parser.parse(source);
 1347    }
 1348    catch (SAXException ex) {
 1349  1 IllegalDataException idex = new IllegalDataException(
 1350    "Malformed internal DTD subset: " + ex.getMessage(), ex);
 1351  1 idex.setData(subset);
 1352  1 throw idex;
 1353    }
 1354    catch (IOException ex) {
 1355  0 throw new RuntimeException("BUG: I don't think this can happen");
 1356    }
 1357   
 1358    }
 1359   
 1360   
 1361    // A reader that immediately returns end of stream. This is a great
 1362    // big hack to avoid reading anything when setting the internal
 1363    // DTD subset. I could use the
 1364    // http://xml.org/sax/features/external-parameter-entities SAX
 1365    // feature, but many parsers don't reliably implement that so
 1366    // instead we simply pretend that all URLs point to empty files.
 1367    private static class EmptyReader extends Reader {
 1368   
 1369  1 public int read(char[] text, int start, int length) throws IOException {
 1370  1 return -1;
 1371    }
 1372   
 1373  1 public void close() {}
 1374   
 1375    }
 1376   
 1377   
 1378    }