1 // Copyright (c) 2011, Mike Samuel 2 // All rights reserved. 3 // 4 // Redistribution and use in source and binary forms, with or without 5 // modification, are permitted provided that the following conditions 6 // are met: 7 // 8 // Redistributions of source code must retain the above copyright 9 // notice, this list of conditions and the following disclaimer. 10 // Redistributions in binary form must reproduce the above copyright 11 // notice, this list of conditions and the following disclaimer in the 12 // documentation and/or other materials provided with the distribution. 13 // Neither the name of the OWASP nor the names of its contributors may 14 // be used to endorse or promote products derived from this software 15 // without specific prior written permission. 16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 19 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 20 // COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 21 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 22 // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 26 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 // POSSIBILITY OF SUCH DAMAGE. 28 29 package org.owasp.html; 30 31 import java.util.List; 32 33 import javax.annotation.Nullable; 34 import javax.annotation.concurrent.Immutable; 35 36 import com.google.common.collect.ImmutableMap; 37 import com.google.common.collect.Lists; 38 39 /** 40 * Wraps an HTML stream event receiver to fill in missing close tags. 41 * If the balancer is given the HTML {@code <p>1<p>2}, the wrapped receiver will 42 * see events equivalent to {@code <p>1</p><p>2</p>}. 43 * 44 * @author Mike Samuel <mikesamuel@gmail.com> 45 */ 46 @TCB 47 public class TagBalancingHtmlStreamEventReceiver 48 implements HtmlStreamEventReceiver { 49 private final HtmlStreamEventReceiver underlying; 50 private int nestingLimit = Integer.MAX_VALUE; 51 private final List<ElementContainmentInfo> openElements 52 = Lists.newArrayList(); 53 TagBalancingHtmlStreamEventReceiver( HtmlStreamEventReceiver underlying)54 public TagBalancingHtmlStreamEventReceiver( 55 HtmlStreamEventReceiver underlying) { 56 this.underlying = underlying; 57 } 58 setNestingLimit(int limit)59 public void setNestingLimit(int limit) { 60 if (openElements.size() > limit) { 61 throw new IllegalStateException(); 62 } 63 this.nestingLimit = limit; 64 } 65 openDocument()66 public void openDocument() { 67 underlying.openDocument(); 68 } 69 closeDocument()70 public void closeDocument() { 71 for (int i = Math.min(nestingLimit, openElements.size()); --i >= 0;) { 72 underlying.closeTag(openElements.get(i).elementName); 73 } 74 openElements.clear(); 75 underlying.closeDocument(); 76 } 77 openTag(String elementName, List<String> attrs)78 public void openTag(String elementName, List<String> attrs) { 79 String canonElementName = HtmlLexer.canonicalName(elementName); 80 ElementContainmentInfo elInfo = ELEMENT_CONTAINMENT_RELATIONSHIPS.get( 81 canonElementName); 82 // Treat unrecognized tags as void, but emit closing tags in closeTag(). 83 if (elInfo == null) { 84 if (openElements.size() < nestingLimit) { 85 underlying.openTag(elementName, attrs); 86 } 87 return; 88 } 89 90 prepareForContent(elInfo); 91 92 if (openElements.size() < nestingLimit) { 93 underlying.openTag(elInfo.elementName, attrs); 94 } 95 if (!elInfo.isVoid) { 96 openElements.add(elInfo); 97 } 98 } 99 prepareForContent(ElementContainmentInfo elInfo)100 private void prepareForContent(ElementContainmentInfo elInfo) { 101 int nOpen = openElements.size(); 102 if (nOpen != 0) { 103 ElementContainmentInfo top = openElements.get(nOpen - 1); 104 if ((top.contents & elInfo.types) == 0) { 105 ElementContainmentInfo blockContainerChild = top.blockContainerChild; 106 // Open implied elements, such as list-items and table cells & rows. 107 if (blockContainerChild != null 108 && (blockContainerChild.contents & elInfo.types) != 0) { 109 underlying.openTag( 110 blockContainerChild.elementName, Lists.<String>newArrayList()); 111 openElements.add(blockContainerChild); 112 top = blockContainerChild; 113 ++nOpen; 114 } 115 } 116 117 // Close all the elements that cannot contain the element to open. 118 List<ElementContainmentInfo> toResumeInReverse = null; 119 while (true) { 120 if ((top.contents & elInfo.types) != 0) { break; } 121 if (openElements.size() < nestingLimit) { 122 underlying.closeTag(top.elementName); 123 } 124 openElements.remove(--nOpen); 125 if (top.resumable) { 126 if (toResumeInReverse == null) { 127 toResumeInReverse = Lists.newArrayList(); 128 } 129 toResumeInReverse.add(top); 130 } 131 if (nOpen == 0) { break; } 132 top = openElements.get(nOpen - 1); 133 } 134 135 if (toResumeInReverse != null) { 136 resume(toResumeInReverse); 137 } 138 } 139 } 140 closeTag(String elementName)141 public void closeTag(String elementName) { 142 String canonElementName = HtmlLexer.canonicalName(elementName); 143 ElementContainmentInfo elInfo = ELEMENT_CONTAINMENT_RELATIONSHIPS.get( 144 canonElementName); 145 if (elInfo == null) { // Allow unrecognized end tags through. 146 if (openElements.size() < nestingLimit) { 147 underlying.closeTag(elementName); 148 } 149 return; 150 } 151 int index = openElements.lastIndexOf(elInfo); 152 // Let any of </h1>, </h2>, ... close other header tags. 153 if (isHeaderElementName(canonElementName)) { 154 for (int i = openElements.size(), limit = index + 1; -- i >= limit;) { 155 ElementContainmentInfo openEl = openElements.get(i); 156 if (isHeaderElementName(openEl.elementName)) { 157 elInfo = openEl; 158 index = i; 159 canonElementName = openEl.elementName; 160 break; 161 } 162 } 163 } 164 if (index < 0) { 165 return; // Don't close unopened tags. 166 } 167 168 // Ensure that index is in the scope of closeable elements. 169 // This approximates the "has an element in *** scope" predicates defined at 170 // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html 171 // #has-an-element-in-the-specific-scope 172 int blockingScopes = elInfo.blockedByScopes; 173 for (int i = openElements.size(); --i > index;) { 174 if ((openElements.get(i).inScopes & blockingScopes) != 0) { 175 return; 176 } 177 } 178 179 int last = openElements.size(); 180 // Close all the elements that cannot contain the element to open. 181 List<ElementContainmentInfo> toResumeInReverse = null; 182 while (--last > index) { 183 ElementContainmentInfo unclosed = openElements.remove(last); 184 if (last + 1 < nestingLimit) { 185 underlying.closeTag(unclosed.elementName); 186 } 187 if (unclosed.resumable) { 188 if (toResumeInReverse == null) { 189 toResumeInReverse = Lists.newArrayList(); 190 } 191 toResumeInReverse.add(unclosed); 192 } 193 } 194 if (openElements.size() < nestingLimit) { 195 underlying.closeTag(elInfo.elementName); 196 } 197 openElements.remove(index); 198 if (toResumeInReverse != null) { 199 resume(toResumeInReverse); 200 } 201 } 202 resume(List<ElementContainmentInfo> toResumeInReverse)203 private void resume(List<ElementContainmentInfo> toResumeInReverse) { 204 for (ElementContainmentInfo toResume : toResumeInReverse) { 205 // TODO: If resuming of things other than plain formatting tags like <b> 206 // and <i>, then we need to store the attributes for resumable tags so 207 // that we can resume with the appropriate attributes. 208 if (openElements.size() < nestingLimit) { 209 underlying.openTag(toResume.elementName, Lists.<String>newArrayList()); 210 } 211 openElements.add(toResume); 212 } 213 } 214 215 private static final long HTML_SPACE_CHAR_BITMASK = 216 (1L << ' ') 217 | (1L << '\t') 218 | (1L << '\n') 219 | (1L << '\u000c') 220 | (1L << '\r'); 221 isInterElementWhitespace(String text)222 public static boolean isInterElementWhitespace(String text) { 223 int n = text.length(); 224 for (int i = 0; i < n; ++i) { 225 int ch = text.charAt(i); 226 if (ch > 0x20 || (HTML_SPACE_CHAR_BITMASK & (1L << ch)) == 0) { 227 return false; 228 } 229 } 230 return true; 231 } 232 text(String text)233 public void text(String text) { 234 if (!isInterElementWhitespace(text)) { 235 prepareForContent(ElementContainmentRelationships.CHARACTER_DATA_ONLY); 236 } 237 238 if (openElements.size() < nestingLimit) { 239 underlying.text(text); 240 } 241 } 242 isHeaderElementName(String canonElementName)243 private static boolean isHeaderElementName(String canonElementName) { 244 return canonElementName.length() == 2 && canonElementName.charAt(0) == 'h' 245 && canonElementName.charAt(1) <= '9'; 246 } 247 248 249 @Immutable 250 private static final class ElementContainmentInfo { 251 final String elementName; 252 /** 253 * True if the adoption agency algorithm allows an element to be resumed 254 * after a mis-nested end tag closes it. 255 * E.g. in {@code <b>Foo<i>Bar</b>Baz</i>} the {@code <i>} element is 256 * resumed after the {@code <b>} element is closed. 257 */ 258 final boolean resumable; 259 /** A set of bits of element groups into which the element falls. */ 260 final int types; 261 /** The type of elements that an element can contain. */ 262 final int contents; 263 /** True if the element has no content -- not even text content. */ 264 final boolean isVoid; 265 /** A legal child of this node that can contain block content. */ 266 final @Nullable ElementContainmentInfo blockContainerChild; 267 /** A bit set of close tag scopes that block this element's close tags. */ 268 final int blockedByScopes; 269 /** A bit set of scopes groups into which this element falls. */ 270 final int inScopes; 271 ElementContainmentInfo( String elementName, boolean resumable, int types, int contents, @Nullable ElementContainmentInfo blockContainerChild, int inScopes)272 ElementContainmentInfo( 273 String elementName, boolean resumable, int types, int contents, 274 @Nullable ElementContainmentInfo blockContainerChild, 275 int inScopes) { 276 this.elementName = elementName; 277 this.resumable = resumable; 278 this.types = types; 279 this.contents = contents; 280 this.isVoid = contents == 0 281 && HtmlTextEscapingMode.isVoidElement(elementName); 282 this.blockContainerChild = blockContainerChild; 283 this.blockedByScopes = 284 ElementContainmentRelationships.CloseTagScope.ALL & ~inScopes; 285 this.inScopes = inScopes; 286 } 287 toString()288 @Override public String toString() { 289 return "<" + elementName + ">"; 290 } 291 } 292 293 static final ImmutableMap<String, ElementContainmentInfo> 294 ELEMENT_CONTAINMENT_RELATIONSHIPS 295 = new ElementContainmentRelationships().toMap(); 296 297 private static class ElementContainmentRelationships { 298 private enum ElementGroup { 299 BLOCK, 300 INLINE, 301 INLINE_MINUS_A, 302 MIXED, 303 TABLE_CONTENT, 304 HEAD_CONTENT, 305 TOP_CONTENT, 306 AREA_ELEMENT, 307 FORM_ELEMENT, 308 LEGEND_ELEMENT, 309 LI_ELEMENT, 310 DL_PART, 311 P_ELEMENT, 312 OPTIONS_ELEMENT, 313 OPTION_ELEMENT, 314 PARAM_ELEMENT, 315 TABLE_ELEMENT, 316 TR_ELEMENT, 317 TD_ELEMENT, 318 COL_ELEMENT, 319 CHARACTER_DATA, 320 ; 321 } 322 323 /** 324 * An identifier for one of the "has a *** element in scope" predicates 325 * used by HTML5 to decide when a close tag implicitly closes tags above 326 * the target element on the open element stack. 327 */ 328 private enum CloseTagScope { 329 COMMON, 330 BUTTON, 331 LIST_ITEM, 332 TABLE, 333 ; 334 335 static final int ALL = (1 << values().length) - 1; 336 } 337 elementGroupBits(ElementGroup a)338 private static int elementGroupBits(ElementGroup a) { 339 return 1 << a.ordinal(); 340 } 341 elementGroupBits( ElementGroup a, ElementGroup b)342 private static int elementGroupBits( 343 ElementGroup a, ElementGroup b) { 344 return (1 << a.ordinal()) | (1 << b.ordinal()); 345 } 346 elementGroupBits( ElementGroup a, ElementGroup b, ElementGroup c)347 private static int elementGroupBits( 348 ElementGroup a, ElementGroup b, ElementGroup c) { 349 return (1 << a.ordinal()) | (1 << b.ordinal()) | (1 << c.ordinal()); 350 } 351 elementGroupBits( ElementGroup... bits)352 private static int elementGroupBits( 353 ElementGroup... bits) { 354 int bitField = 0; 355 for (ElementGroup bit : bits) { 356 bitField |= (1 << bit.ordinal()); 357 } 358 return bitField; 359 } 360 scopeBits(CloseTagScope a)361 private static int scopeBits(CloseTagScope a) { 362 return 1 << a.ordinal(); 363 } 364 scopeBits( CloseTagScope a, CloseTagScope b, CloseTagScope c)365 private static int scopeBits( 366 CloseTagScope a, CloseTagScope b, CloseTagScope c) { 367 return (1 << a.ordinal()) | (1 << b.ordinal()) | (1 << c.ordinal()); 368 } 369 370 private ImmutableMap.Builder<String, ElementContainmentInfo> definitions 371 = ImmutableMap.builder(); 372 defineElement( String elementName, boolean resumable, int types, int contentTypes)373 private ElementContainmentInfo defineElement( 374 String elementName, boolean resumable, int types, int contentTypes) { 375 return defineElement(elementName, resumable, types, contentTypes, null); 376 } 377 defineElement( String elementName, boolean resumable, int types, int contentTypes, int inScopes)378 private ElementContainmentInfo defineElement( 379 String elementName, boolean resumable, int types, int contentTypes, 380 int inScopes) { 381 return defineElement( 382 elementName, resumable, types, contentTypes, null, inScopes); 383 } 384 defineElement( String elementName, boolean resumable, int types, int contentTypes, @Nullable ElementContainmentInfo blockContainer)385 private ElementContainmentInfo defineElement( 386 String elementName, boolean resumable, int types, int contentTypes, 387 @Nullable ElementContainmentInfo blockContainer) { 388 return defineElement( 389 elementName, resumable, types, contentTypes, blockContainer, 0); 390 } 391 defineElement( String elementName, boolean resumable, int types, int contentTypes, @Nullable ElementContainmentInfo blockContainer, int inScopes)392 private ElementContainmentInfo defineElement( 393 String elementName, boolean resumable, int types, int contentTypes, 394 @Nullable ElementContainmentInfo blockContainer, int inScopes) { 395 ElementContainmentInfo info = new ElementContainmentInfo( 396 elementName, resumable, types, contentTypes, blockContainer, 397 inScopes); 398 definitions.put(elementName, info); 399 return info; 400 } 401 toMap()402 private ImmutableMap<String, ElementContainmentInfo> toMap() { 403 return definitions.build(); 404 } 405 406 { 407 defineElement( 408 "a", false, elementGroupBits( 409 ElementGroup.INLINE 410 ), elementGroupBits( 411 ElementGroup.INLINE_MINUS_A 412 )); 413 defineElement( 414 "abbr", true, elementGroupBits( 415 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 416 ), elementGroupBits( 417 ElementGroup.INLINE 418 )); 419 defineElement( 420 "acronym", true, elementGroupBits( 421 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 422 ), elementGroupBits( 423 ElementGroup.INLINE 424 )); 425 defineElement( 426 "address", false, elementGroupBits( 427 ElementGroup.BLOCK 428 ), elementGroupBits( 429 ElementGroup.INLINE, ElementGroup.P_ELEMENT 430 )); 431 defineElement( 432 "applet", false, elementGroupBits( 433 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 434 ), elementGroupBits( 435 ElementGroup.BLOCK, ElementGroup.INLINE, 436 ElementGroup.PARAM_ELEMENT 437 ), scopeBits( 438 CloseTagScope.COMMON, CloseTagScope.BUTTON, 439 CloseTagScope.LIST_ITEM 440 )); 441 defineElement( 442 "area", false, elementGroupBits(ElementGroup.AREA_ELEMENT), 0); 443 defineElement( 444 "audio", false, elementGroupBits( 445 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 446 ), 0); 447 defineElement( 448 "b", true, elementGroupBits( 449 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 450 ), elementGroupBits( 451 ElementGroup.INLINE 452 )); 453 defineElement( 454 "base", false, elementGroupBits(ElementGroup.HEAD_CONTENT), 0); 455 defineElement( 456 "basefont", false, elementGroupBits( 457 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 458 ), 0); 459 defineElement( 460 "bdi", true, elementGroupBits( 461 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 462 ), elementGroupBits( 463 ElementGroup.INLINE 464 )); 465 defineElement( 466 "bdo", true, elementGroupBits( 467 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 468 ), elementGroupBits( 469 ElementGroup.INLINE 470 )); 471 defineElement( 472 "big", true, elementGroupBits( 473 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 474 ), elementGroupBits( 475 ElementGroup.INLINE 476 )); 477 defineElement( 478 "blink", true, elementGroupBits( 479 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 480 ), elementGroupBits( 481 ElementGroup.INLINE 482 )); 483 defineElement( 484 "blockquote", false, elementGroupBits( 485 ElementGroup.BLOCK 486 ), elementGroupBits( 487 ElementGroup.BLOCK, ElementGroup.INLINE 488 )); 489 defineElement( 490 "body", false, elementGroupBits( 491 ElementGroup.TOP_CONTENT 492 ), elementGroupBits( 493 ElementGroup.BLOCK, ElementGroup.INLINE 494 )); 495 defineElement( 496 "br", false, elementGroupBits( 497 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 498 ), 0); 499 defineElement( 500 "button", false, elementGroupBits( 501 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 502 ), elementGroupBits( 503 ElementGroup.BLOCK, ElementGroup.INLINE 504 ), scopeBits(CloseTagScope.BUTTON)); 505 defineElement( 506 "canvas", false, elementGroupBits( 507 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 508 ), elementGroupBits( 509 ElementGroup.INLINE 510 )); 511 defineElement( 512 "caption", false, elementGroupBits( 513 ElementGroup.TABLE_CONTENT 514 ), elementGroupBits( 515 ElementGroup.INLINE 516 ), scopeBits( 517 CloseTagScope.COMMON, CloseTagScope.BUTTON, 518 CloseTagScope.LIST_ITEM 519 )); 520 defineElement( 521 "center", false, elementGroupBits( 522 ElementGroup.BLOCK 523 ), elementGroupBits( 524 ElementGroup.BLOCK, ElementGroup.INLINE 525 )); 526 defineElement( 527 "cite", true, elementGroupBits( 528 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 529 ), elementGroupBits( 530 ElementGroup.INLINE 531 )); 532 defineElement( 533 "code", true, elementGroupBits( 534 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 535 ), elementGroupBits( 536 ElementGroup.INLINE 537 )); 538 defineElement( 539 "col", false, elementGroupBits( 540 ElementGroup.TABLE_CONTENT, ElementGroup.COL_ELEMENT 541 ), 0); 542 defineElement( 543 "colgroup", false, elementGroupBits( 544 ElementGroup.TABLE_CONTENT 545 ), elementGroupBits( 546 ElementGroup.COL_ELEMENT 547 )); 548 ElementContainmentInfo DD = defineElement( 549 "dd", false, elementGroupBits( 550 ElementGroup.DL_PART 551 ), elementGroupBits( 552 ElementGroup.BLOCK, ElementGroup.INLINE 553 )); 554 defineElement( 555 "del", true, elementGroupBits( 556 ElementGroup.BLOCK, ElementGroup.INLINE, 557 ElementGroup.MIXED 558 ), elementGroupBits( 559 ElementGroup.BLOCK, ElementGroup.INLINE 560 )); 561 defineElement( 562 "dfn", true, elementGroupBits( 563 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 564 ), elementGroupBits( 565 ElementGroup.INLINE 566 )); 567 defineElement( 568 "dir", false, elementGroupBits( 569 ElementGroup.BLOCK 570 ), elementGroupBits( 571 ElementGroup.LI_ELEMENT 572 )); 573 defineElement( 574 "div", false, elementGroupBits( 575 ElementGroup.BLOCK 576 ), elementGroupBits( 577 ElementGroup.BLOCK, ElementGroup.INLINE 578 )); 579 defineElement( 580 "dl", false, elementGroupBits( 581 ElementGroup.BLOCK 582 ), elementGroupBits( 583 ElementGroup.DL_PART 584 ), 585 DD); 586 defineElement( 587 "dt", false, elementGroupBits( 588 ElementGroup.DL_PART 589 ), elementGroupBits( 590 ElementGroup.INLINE 591 )); 592 defineElement( 593 "em", true, elementGroupBits( 594 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 595 ), elementGroupBits( 596 ElementGroup.INLINE 597 )); 598 defineElement( 599 "fieldset", false, elementGroupBits( 600 ElementGroup.BLOCK 601 ), elementGroupBits( 602 ElementGroup.BLOCK, ElementGroup.INLINE, 603 ElementGroup.LEGEND_ELEMENT 604 )); 605 defineElement( 606 "font", false, elementGroupBits( 607 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 608 ), elementGroupBits( 609 ElementGroup.INLINE 610 )); 611 defineElement( 612 "form", false, elementGroupBits( 613 ElementGroup.BLOCK, ElementGroup.FORM_ELEMENT 614 ), elementGroupBits( 615 ElementGroup.BLOCK, ElementGroup.INLINE, 616 ElementGroup.INLINE_MINUS_A, ElementGroup.TR_ELEMENT, 617 ElementGroup.TD_ELEMENT 618 )); 619 defineElement( 620 "h1", false, elementGroupBits( 621 ElementGroup.BLOCK 622 ), elementGroupBits( 623 ElementGroup.INLINE 624 )); 625 defineElement( 626 "h2", false, elementGroupBits( 627 ElementGroup.BLOCK 628 ), elementGroupBits( 629 ElementGroup.INLINE 630 )); 631 defineElement( 632 "h3", false, elementGroupBits( 633 ElementGroup.BLOCK 634 ), elementGroupBits( 635 ElementGroup.INLINE 636 )); 637 defineElement( 638 "h4", false, elementGroupBits( 639 ElementGroup.BLOCK 640 ), elementGroupBits( 641 ElementGroup.INLINE 642 )); 643 defineElement( 644 "h5", false, elementGroupBits( 645 ElementGroup.BLOCK 646 ), elementGroupBits( 647 ElementGroup.INLINE 648 )); 649 defineElement( 650 "h6", false, elementGroupBits( 651 ElementGroup.BLOCK 652 ), elementGroupBits( 653 ElementGroup.INLINE 654 )); 655 defineElement( 656 "head", false, elementGroupBits( 657 ElementGroup.TOP_CONTENT 658 ), elementGroupBits( 659 ElementGroup.HEAD_CONTENT 660 )); 661 defineElement( 662 "hr", false, elementGroupBits(ElementGroup.BLOCK), 0); 663 defineElement( 664 "html", false, 0, elementGroupBits(ElementGroup.TOP_CONTENT), 665 CloseTagScope.ALL); 666 defineElement( 667 "i", true, elementGroupBits( 668 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 669 ), elementGroupBits( 670 ElementGroup.INLINE 671 )); 672 defineElement( 673 "iframe", false, elementGroupBits( 674 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 675 ), elementGroupBits( 676 ElementGroup.BLOCK, ElementGroup.INLINE 677 )); 678 defineElement( 679 "img", false, elementGroupBits( 680 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 681 ), 0); 682 defineElement( 683 "input", false, elementGroupBits( 684 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 685 ), 0); 686 defineElement( 687 "ins", true, elementGroupBits( 688 ElementGroup.BLOCK, ElementGroup.INLINE 689 ), elementGroupBits( 690 ElementGroup.BLOCK, ElementGroup.INLINE 691 )); 692 defineElement( 693 "isindex", false, elementGroupBits(ElementGroup.INLINE), 0); 694 defineElement( 695 "kbd", true, elementGroupBits( 696 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 697 ), elementGroupBits( 698 ElementGroup.INLINE 699 )); 700 defineElement( 701 "label", false, elementGroupBits( 702 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 703 ), elementGroupBits( 704 ElementGroup.INLINE 705 )); 706 defineElement( 707 "legend", false, elementGroupBits( 708 ElementGroup.LEGEND_ELEMENT 709 ), elementGroupBits( 710 ElementGroup.INLINE 711 )); 712 ElementContainmentInfo LI = defineElement( 713 "li", false, elementGroupBits( 714 ElementGroup.LI_ELEMENT 715 ), elementGroupBits( 716 ElementGroup.BLOCK, ElementGroup.INLINE 717 )); 718 defineElement( 719 "link", false, elementGroupBits( 720 ElementGroup.INLINE, ElementGroup.HEAD_CONTENT 721 ), 0); 722 defineElement( 723 "listing", false, elementGroupBits( 724 ElementGroup.BLOCK 725 ), elementGroupBits( 726 ElementGroup.INLINE 727 )); 728 defineElement( 729 "map", false, elementGroupBits( 730 ElementGroup.INLINE 731 ), elementGroupBits( 732 ElementGroup.BLOCK, ElementGroup.AREA_ELEMENT 733 )); 734 defineElement( 735 "meta", false, elementGroupBits(ElementGroup.HEAD_CONTENT), 0); 736 defineElement( 737 "nobr", false, elementGroupBits( 738 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 739 ), elementGroupBits( 740 ElementGroup.INLINE 741 )); 742 defineElement( 743 "noframes", false, elementGroupBits( 744 ElementGroup.BLOCK, ElementGroup.TOP_CONTENT 745 ), elementGroupBits( 746 ElementGroup.BLOCK, ElementGroup.INLINE, 747 ElementGroup.TOP_CONTENT 748 )); 749 defineElement( 750 "noscript", false, elementGroupBits( 751 ElementGroup.BLOCK 752 ), elementGroupBits( 753 ElementGroup.BLOCK, ElementGroup.INLINE 754 )); 755 defineElement( 756 "object", false, elementGroupBits( 757 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A, 758 ElementGroup.HEAD_CONTENT 759 ), elementGroupBits( 760 ElementGroup.BLOCK, ElementGroup.INLINE, 761 ElementGroup.PARAM_ELEMENT 762 ), scopeBits( 763 CloseTagScope.COMMON, CloseTagScope.BUTTON, 764 CloseTagScope.LIST_ITEM 765 )); 766 defineElement( 767 "ol", false, elementGroupBits( 768 ElementGroup.BLOCK 769 ), elementGroupBits( 770 ElementGroup.LI_ELEMENT 771 ), 772 LI, 773 scopeBits(CloseTagScope.LIST_ITEM)); 774 defineElement( 775 "optgroup", false, elementGroupBits( 776 ElementGroup.OPTIONS_ELEMENT 777 ), elementGroupBits( 778 ElementGroup.OPTIONS_ELEMENT 779 )); 780 defineElement( 781 "option", false, elementGroupBits( 782 ElementGroup.OPTIONS_ELEMENT, ElementGroup.OPTION_ELEMENT 783 ), elementGroupBits( 784 ElementGroup.CHARACTER_DATA 785 )); 786 defineElement( 787 "p", false, elementGroupBits( 788 ElementGroup.BLOCK, ElementGroup.P_ELEMENT 789 ), elementGroupBits( 790 ElementGroup.INLINE, ElementGroup.TABLE_ELEMENT 791 )); 792 defineElement( 793 "param", false, elementGroupBits(ElementGroup.PARAM_ELEMENT), 0); 794 defineElement( 795 "pre", false, elementGroupBits( 796 ElementGroup.BLOCK 797 ), elementGroupBits( 798 ElementGroup.INLINE 799 )); 800 defineElement( 801 "q", true, elementGroupBits( 802 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 803 ), elementGroupBits( 804 ElementGroup.INLINE 805 )); 806 defineElement( 807 "s", true, elementGroupBits( 808 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 809 ), elementGroupBits( 810 ElementGroup.INLINE 811 )); 812 defineElement( 813 "samp", true, elementGroupBits( 814 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 815 ), elementGroupBits( 816 ElementGroup.INLINE 817 )); 818 defineElement( 819 "script", false, elementGroupBits( 820 ElementGroup.BLOCK, ElementGroup.INLINE, 821 ElementGroup.INLINE_MINUS_A, ElementGroup.MIXED, 822 ElementGroup.TABLE_CONTENT, ElementGroup.HEAD_CONTENT, 823 ElementGroup.TOP_CONTENT, ElementGroup.AREA_ELEMENT, 824 ElementGroup.FORM_ELEMENT, ElementGroup.LEGEND_ELEMENT, 825 ElementGroup.LI_ELEMENT, ElementGroup.DL_PART, 826 ElementGroup.P_ELEMENT, ElementGroup.OPTIONS_ELEMENT, 827 ElementGroup.OPTION_ELEMENT, ElementGroup.PARAM_ELEMENT, 828 ElementGroup.TABLE_ELEMENT, ElementGroup.TR_ELEMENT, 829 ElementGroup.TD_ELEMENT, ElementGroup.COL_ELEMENT 830 ), elementGroupBits( 831 ElementGroup.CHARACTER_DATA)); 832 defineElement( 833 "select", false, elementGroupBits( 834 ElementGroup.INLINE 835 ), elementGroupBits( 836 ElementGroup.OPTIONS_ELEMENT 837 )); 838 defineElement( 839 "small", true, elementGroupBits( 840 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 841 ), elementGroupBits( 842 ElementGroup.INLINE 843 )); 844 defineElement( 845 "span", false, elementGroupBits( 846 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 847 ), elementGroupBits( 848 ElementGroup.INLINE 849 )); 850 defineElement( 851 "strike", true, elementGroupBits( 852 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 853 ), elementGroupBits( 854 ElementGroup.INLINE 855 )); 856 defineElement( 857 "strong", true, elementGroupBits( 858 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 859 ), elementGroupBits( 860 ElementGroup.INLINE 861 )); 862 defineElement( 863 "style", false, elementGroupBits( 864 ElementGroup.INLINE, ElementGroup.HEAD_CONTENT 865 ), elementGroupBits( 866 ElementGroup.CHARACTER_DATA 867 )); 868 defineElement( 869 "sub", true, elementGroupBits( 870 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 871 ), elementGroupBits( 872 ElementGroup.INLINE 873 )); 874 defineElement( 875 "sup", true, elementGroupBits( 876 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 877 ), elementGroupBits( 878 ElementGroup.INLINE 879 )); 880 defineElement( 881 "table", false, elementGroupBits( 882 ElementGroup.BLOCK, ElementGroup.TABLE_ELEMENT 883 ), elementGroupBits( 884 ElementGroup.TABLE_CONTENT, ElementGroup.FORM_ELEMENT 885 ), CloseTagScope.ALL); 886 defineElement( 887 "tbody", false, elementGroupBits( 888 ElementGroup.TABLE_CONTENT 889 ), elementGroupBits( 890 ElementGroup.TR_ELEMENT 891 )); 892 ElementContainmentInfo TD = defineElement( 893 "td", false, elementGroupBits( 894 ElementGroup.TD_ELEMENT 895 ), elementGroupBits( 896 ElementGroup.BLOCK, ElementGroup.INLINE 897 ), scopeBits( 898 CloseTagScope.COMMON, CloseTagScope.BUTTON, 899 CloseTagScope.LIST_ITEM 900 )); 901 defineElement( 902 "textarea", false, 903 // No, a textarea cannot be inside a link. 904 elementGroupBits(ElementGroup.INLINE), 905 elementGroupBits(ElementGroup.CHARACTER_DATA)); 906 defineElement( 907 "tfoot", false, elementGroupBits( 908 ElementGroup.TABLE_CONTENT 909 ), elementGroupBits( 910 ElementGroup.FORM_ELEMENT, ElementGroup.TR_ELEMENT, 911 ElementGroup.TD_ELEMENT 912 )); 913 defineElement( 914 "th", false, elementGroupBits( 915 ElementGroup.TD_ELEMENT 916 ), elementGroupBits( 917 ElementGroup.BLOCK, ElementGroup.INLINE 918 ), scopeBits( 919 CloseTagScope.COMMON, CloseTagScope.BUTTON, 920 CloseTagScope.LIST_ITEM 921 )); 922 defineElement( 923 "thead", false, elementGroupBits( 924 ElementGroup.TABLE_CONTENT 925 ), elementGroupBits( 926 ElementGroup.FORM_ELEMENT, ElementGroup.TR_ELEMENT, 927 ElementGroup.TD_ELEMENT 928 )); 929 defineElement( 930 "title", false, elementGroupBits(ElementGroup.HEAD_CONTENT), 931 elementGroupBits(ElementGroup.CHARACTER_DATA)); 932 defineElement( 933 "tr", false, elementGroupBits( 934 ElementGroup.TABLE_CONTENT, ElementGroup.TR_ELEMENT 935 ), elementGroupBits( 936 ElementGroup.FORM_ELEMENT, ElementGroup.TD_ELEMENT 937 ), 938 TD); 939 defineElement( 940 "tt", true, elementGroupBits( 941 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 942 ), elementGroupBits( 943 ElementGroup.INLINE 944 )); 945 defineElement( 946 "u", true, elementGroupBits( 947 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 948 ), elementGroupBits( 949 ElementGroup.INLINE 950 )); 951 defineElement( 952 "ul", false, elementGroupBits( 953 ElementGroup.BLOCK 954 ), elementGroupBits( 955 ElementGroup.LI_ELEMENT 956 ), 957 LI, 958 scopeBits(CloseTagScope.LIST_ITEM)); 959 defineElement( 960 "var", false, elementGroupBits( 961 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 962 ), elementGroupBits( 963 ElementGroup.INLINE 964 )); 965 defineElement( 966 "video", false, elementGroupBits( 967 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 968 ), 0); 969 defineElement( 970 "wbr", false, elementGroupBits( 971 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A 972 ), 0); 973 defineElement( 974 "xmp", false, elementGroupBits( 975 ElementGroup.BLOCK 976 ), elementGroupBits( 977 ElementGroup.INLINE 978 )); 979 980 } 981 982 private static final ElementContainmentInfo CHARACTER_DATA_ONLY 983 = new ElementContainmentInfo( 984 "#text", false, 985 elementGroupBits( 986 ElementGroup.INLINE, ElementGroup.INLINE_MINUS_A, 987 ElementGroup.BLOCK, ElementGroup.CHARACTER_DATA), 988 0, null, 0); 989 } 990 allowsPlainTextualContent(String canonElementName)991 static boolean allowsPlainTextualContent(String canonElementName) { 992 ElementContainmentInfo info = 993 ELEMENT_CONTAINMENT_RELATIONSHIPS.get(canonElementName); 994 if (info == null 995 || ((info.contents 996 & ElementContainmentRelationships.CHARACTER_DATA_ONLY.types) 997 != 0)) { 998 switch (HtmlTextEscapingMode.getModeForTag(canonElementName)) { 999 case PCDATA: return true; 1000 case RCDATA: return true; 1001 case PLAIN_TEXT: return true; 1002 case VOID: return false; 1003 case CDATA: 1004 case CDATA_SOMETIMES: 1005 return "xmp".equals(canonElementName) 1006 || "listing".equals(canonElementName); 1007 } 1008 } 1009 return false; 1010 } 1011 } 1012