1: <?php
    2: 
    3:     4:     5:     6:     7:     8:     9:    10:    11:    12:    13:    14:    15:    16:    17:    18:    19: 
   20: 
   21: 
   22: 
   23:    24:    25:    26:    27:    28:    29:    30:    31:    32:    33:    34:    35:    36:    37:    38:    39: 
   40: 
   41:    42:    43:    44:    45:    46:    47:    48:    49:    50:    51:    52:    53:    54:    55:    56:    57:    58: 
   59: 
   60:    61:    62:    63:    64:    65:    66:    67:    68:    69:    70:    71:    72:    73: 
   74: class HTMLPurifier
   75: {
   76: 
   77:        78:    79:    80: 
   81:     public $version = '4.6.0';
   82: 
   83:        84:    85: 
   86:     const VERSION = '4.6.0';
   87: 
   88:        89:    90:    91: 
   92:     public $config;
   93: 
   94:        95:    96:    97:    98: 
   99:     private $filters = array();
  100: 
  101:       102:   103:   104: 
  105:     private static $instance;
  106: 
  107:       108:   109: 
  110:     protected $strategy;
  111: 
  112:       113:   114: 
  115:     protected $generator;
  116: 
  117:       118:   119:   120:   121: 
  122:     public $context;
  123: 
  124:       125:   126:   127:   128:   129:   130:   131:   132:   133: 
  134:     public function __construct($config = null)
  135:     {
  136:         $this->config = HTMLPurifier_Config::create($config);
  137:         $this->strategy = new HTMLPurifier_Strategy_Core();
  138:     }
  139: 
  140:       141:   142:   143:   144: 
  145:     public function addFilter($filter)
  146:     {
  147:         trigger_error(
  148:             'HTMLPurifier->addFilter() is deprecated, use configuration directives' .
  149:             ' in the Filter namespace or Filter.Custom',
  150:             E_USER_WARNING
  151:         );
  152:         $this->filters[] = $filter;
  153:     }
  154: 
  155:       156:   157:   158:   159:   160:   161:   162:   163:   164:   165: 
  166:     public function purify($html, $config = null)
  167:     {
  168:         
  169:         $config = $config ? HTMLPurifier_Config::create($config) : $this->config;
  170: 
  171:         
  172:         
  173:         $lexer = HTMLPurifier_Lexer::create($config);
  174: 
  175:         $context = new HTMLPurifier_Context();
  176: 
  177:         
  178:         $this->generator = new HTMLPurifier_Generator($config, $context);
  179:         $context->register('Generator', $this->generator);
  180: 
  181:         
  182:         if ($config->get('Core.CollectErrors')) {
  183:             
  184:             $language_factory = HTMLPurifier_LanguageFactory::instance();
  185:             $language = $language_factory->create($config, $context);
  186:             $context->register('Locale', $language);
  187: 
  188:             $error_collector = new HTMLPurifier_ErrorCollector($context);
  189:             $context->register('ErrorCollector', $error_collector);
  190:         }
  191: 
  192:         
  193:         
  194:         $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context);
  195:         $context->register('IDAccumulator', $id_accumulator);
  196: 
  197:         $html = HTMLPurifier_Encoder::convertToUTF8($html, $config, $context);
  198: 
  199:         
  200:         $filter_flags = $config->getBatch('Filter');
  201:         $custom_filters = $filter_flags['Custom'];
  202:         unset($filter_flags['Custom']);
  203:         $filters = array();
  204:         foreach ($filter_flags as $filter => $flag) {
  205:             if (!$flag) {
  206:                 continue;
  207:             }
  208:             if (strpos($filter, '.') !== false) {
  209:                 continue;
  210:             }
  211:             $class = "HTMLPurifier_Filter_$filter";
  212:             $filters[] = new $class;
  213:         }
  214:         foreach ($custom_filters as $filter) {
  215:             
  216:             $filters[] = $filter;
  217:         }
  218:         $filters = array_merge($filters, $this->filters);
  219:         
  220: 
  221:         for ($i = 0, $filter_size = count($filters); $i < $filter_size; $i++) {
  222:             $html = $filters[$i]->preFilter($html, $config, $context);
  223:         }
  224: 
  225:         
  226:         $html =
  227:             $this->generator->generateFromTokens(
  228:                 
  229:                 $this->strategy->execute(
  230:                     
  231:                     $lexer->tokenizeHTML(
  232:                         
  233:                         $html,
  234:                         $config,
  235:                         $context
  236:                     ),
  237:                     $config,
  238:                     $context
  239:                 )
  240:             );
  241: 
  242:         for ($i = $filter_size - 1; $i >= 0; $i--) {
  243:             $html = $filters[$i]->postFilter($html, $config, $context);
  244:         }
  245: 
  246:         $html = HTMLPurifier_Encoder::convertFromUTF8($html, $config, $context);
  247:         $this->context =& $context;
  248:         return $html;
  249:     }
  250: 
  251:       252:   253:   254:   255:   256:   257:   258:   259: 
  260:     public function purifyArray($array_of_html, $config = null)
  261:     {
  262:         $context_array = array();
  263:         foreach ($array_of_html as $key => $html) {
  264:             $array_of_html[$key] = $this->purify($html, $config);
  265:             $context_array[$key] = $this->context;
  266:         }
  267:         $this->context = $context_array;
  268:         return $array_of_html;
  269:     }
  270: 
  271:       272:   273:   274:   275:   276:   277:   278:   279:   280: 
  281:     public static function instance($prototype = null)
  282:     {
  283:         if (!self::$instance || $prototype) {
  284:             if ($prototype instanceof HTMLPurifier) {
  285:                 self::$instance = $prototype;
  286:             } elseif ($prototype) {
  287:                 self::$instance = new HTMLPurifier($prototype);
  288:             } else {
  289:                 self::$instance = new HTMLPurifier();
  290:             }
  291:         }
  292:         return self::$instance;
  293:     }
  294: 
  295:       296:   297:   298:   299:   300:   301:   302:   303:   304:   305: 
  306:     public static function getInstance($prototype = null)
  307:     {
  308:         return HTMLPurifier::instance($prototype);
  309:     }
  310: }
  311: 
  312: 
  313: 
  314: 
  315: 
  316:   317:   318:   319:   320:   321:   322: 
  323: class HTMLPurifier_Arborize
  324: {
  325:     public static function arborize($tokens, $config, $context) {
  326:         $definition = $config->getHTMLDefinition();
  327:         $parent = new HTMLPurifier_Token_Start($definition->info_parent);
  328:         $stack = array($parent->toNode());
  329:         foreach ($tokens as $token) {
  330:             $token->skip = null; 
  331:             $token->carryover = null; 
  332:             if ($token instanceof HTMLPurifier_Token_End) {
  333:                 $token->start = null; 
  334:                 $r = array_pop($stack);
  335:                 assert($r->name === $token->name);
  336:                 assert(empty($token->attr));
  337:                 $r->endCol = $token->col;
  338:                 $r->endLine = $token->line;
  339:                 $r->endArmor = $token->armor;
  340:                 continue;
  341:             }
  342:             $node = $token->toNode();
  343:             $stack[count($stack)-1]->children[] = $node;
  344:             if ($token instanceof HTMLPurifier_Token_Start) {
  345:                 $stack[] = $node;
  346:             }
  347:         }
  348:         assert(count($stack) == 1);
  349:         return $stack[0];
  350:     }
  351: 
  352:     public static function flatten($node, $config, $context) {
  353:         $level = 0;
  354:         $nodes = array($level => new HTMLPurifier_Queue(array($node)));
  355:         $closingTokens = array();
  356:         $tokens = array();
  357:         do {
  358:             while (!$nodes[$level]->isEmpty()) {
  359:                 $node = $nodes[$level]->shift(); 
  360:                 list($start, $end) = $node->toTokenPair();
  361:                 if ($level > 0) {
  362:                     $tokens[] = $start;
  363:                 }
  364:                 if ($end !== NULL) {
  365:                     $closingTokens[$level][] = $end;
  366:                 }
  367:                 if ($node instanceof HTMLPurifier_Node_Element) {
  368:                     $level++;
  369:                     $nodes[$level] = new HTMLPurifier_Queue();
  370:                     foreach ($node->children as $childNode) {
  371:                         $nodes[$level]->push($childNode);
  372:                     }
  373:                 }
  374:             }
  375:             $level--;
  376:             if ($level && isset($closingTokens[$level])) {
  377:                 while ($token = array_pop($closingTokens[$level])) {
  378:                     $tokens[] = $token;
  379:                 }
  380:             }
  381:         } while ($level > 0);
  382:         return $tokens;
  383:     }
  384: }
  385: 
  386: 
  387: 
  388:   389:   390: 
  391: 
  392: class HTMLPurifier_AttrCollections
  393: {
  394: 
  395:       396:   397:   398: 
  399:     public $info = array();
  400: 
  401:       402:   403:   404:   405:   406:   407: 
  408:     public function __construct($attr_types, $modules)
  409:     {
  410:         
  411:         foreach ($modules as $module) {
  412:             foreach ($module->attr_collections as $coll_i => $coll) {
  413:                 if (!isset($this->info[$coll_i])) {
  414:                     $this->info[$coll_i] = array();
  415:                 }
  416:                 foreach ($coll as $attr_i => $attr) {
  417:                     if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) {
  418:                         
  419:                         $this->info[$coll_i][$attr_i] = array_merge(
  420:                             $this->info[$coll_i][$attr_i],
  421:                             $attr
  422:                         );
  423:                         continue;
  424:                     }
  425:                     $this->info[$coll_i][$attr_i] = $attr;
  426:                 }
  427:             }
  428:         }
  429:         
  430:         foreach ($this->info as $name => $attr) {
  431:             
  432:             $this->performInclusions($this->info[$name]);
  433:             
  434:             $this->expandIdentifiers($this->info[$name], $attr_types);
  435:         }
  436:     }
  437: 
  438:       439:   440:   441:   442: 
  443:     public function performInclusions(&$attr)
  444:     {
  445:         if (!isset($attr[0])) {
  446:             return;
  447:         }
  448:         $merge = $attr[0];
  449:         $seen  = array(); 
  450:         
  451:         for ($i = 0; isset($merge[$i]); $i++) {
  452:             if (isset($seen[$merge[$i]])) {
  453:                 continue;
  454:             }
  455:             $seen[$merge[$i]] = true;
  456:             
  457:             if (!isset($this->info[$merge[$i]])) {
  458:                 continue;
  459:             }
  460:             foreach ($this->info[$merge[$i]] as $key => $value) {
  461:                 if (isset($attr[$key])) {
  462:                     continue;
  463:                 } 
  464:                 $attr[$key] = $value;
  465:             }
  466:             if (isset($this->info[$merge[$i]][0])) {
  467:                 
  468:                 $merge = array_merge($merge, $this->info[$merge[$i]][0]);
  469:             }
  470:         }
  471:         unset($attr[0]);
  472:     }
  473: 
  474:       475:   476:   477:   478:   479: 
  480:     public function expandIdentifiers(&$attr, $attr_types)
  481:     {
  482:         
  483:         
  484:         $processed = array();
  485: 
  486:         foreach ($attr as $def_i => $def) {
  487:             
  488:             if ($def_i === 0) {
  489:                 continue;
  490:             }
  491: 
  492:             if (isset($processed[$def_i])) {
  493:                 continue;
  494:             }
  495: 
  496:             
  497:             if ($required = (strpos($def_i, '*') !== false)) {
  498:                 
  499:                 unset($attr[$def_i]);
  500:                 $def_i = trim($def_i, '*');
  501:                 $attr[$def_i] = $def;
  502:             }
  503: 
  504:             $processed[$def_i] = true;
  505: 
  506:             
  507:             if (is_object($def)) {
  508:                 
  509:                 $attr[$def_i]->required = ($required || $attr[$def_i]->required);
  510:                 continue;
  511:             }
  512: 
  513:             if ($def === false) {
  514:                 unset($attr[$def_i]);
  515:                 continue;
  516:             }
  517: 
  518:             if ($t = $attr_types->get($def)) {
  519:                 $attr[$def_i] = $t;
  520:                 $attr[$def_i]->required = $required;
  521:             } else {
  522:                 unset($attr[$def_i]);
  523:             }
  524:         }
  525:     }
  526: }
  527: 
  528: 
  529: 
  530: 
  531: 
  532:   533:   534:   535:   536:   537:   538:   539:   540: 
  541: 
  542: abstract class HTMLPurifier_AttrDef
  543: {
  544: 
  545:       546:   547:   548:   549: 
  550:     public $minimized = false;
  551: 
  552:       553:   554:   555:   556: 
  557:     public $required = false;
  558: 
  559:       560:   561:   562:   563:   564:   565: 
  566:     abstract public function validate($string, $config, $context);
  567: 
  568:       569:   570:   571:   572:   573:   574:   575:   576:   577:   578:   579:   580:   581:   582:   583:   584:   585:   586:   587:   588: 
  589:     public function parseCDATA($string)
  590:     {
  591:         $string = trim($string);
  592:         $string = str_replace(array("\n", "\t", "\r"), ' ', $string);
  593:         return $string;
  594:     }
  595: 
  596:       597:   598:   599:   600: 
  601:     public function make($string)
  602:     {
  603:         
  604:         
  605:         
  606:         
  607:         return $this;
  608:     }
  609: 
  610:       611:   612:   613:   614:   615: 
  616:     protected function mungeRgb($string)
  617:     {
  618:         return preg_replace('/rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\)/', 'rgb(\1,\2,\3)', $string);
  619:     }
  620: 
  621:       622:   623:   624: 
  625:     protected function expandCSSEscape($string)
  626:     {
  627:         
  628:         $ret = '';
  629:         for ($i = 0, $c = strlen($string); $i < $c; $i++) {
  630:             if ($string[$i] === '\\') {
  631:                 $i++;
  632:                 if ($i >= $c) {
  633:                     $ret .= '\\';
  634:                     break;
  635:                 }
  636:                 if (ctype_xdigit($string[$i])) {
  637:                     $code = $string[$i];
  638:                     for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) {
  639:                         if (!ctype_xdigit($string[$i])) {
  640:                             break;
  641:                         }
  642:                         $code .= $string[$i];
  643:                     }
  644:                     
  645:                     
  646:                     
  647:                     $char = HTMLPurifier_Encoder::unichr(hexdec($code));
  648:                     if (HTMLPurifier_Encoder::cleanUTF8($char) === '') {
  649:                         continue;
  650:                     }
  651:                     $ret .= $char;
  652:                     if ($i < $c && trim($string[$i]) !== '') {
  653:                         $i--;
  654:                     }
  655:                     continue;
  656:                 }
  657:                 if ($string[$i] === "\n") {
  658:                     continue;
  659:                 }
  660:             }
  661:             $ret .= $string[$i];
  662:         }
  663:         return $ret;
  664:     }
  665: }
  666: 
  667: 
  668: 
  669: 
  670: 
  671:   672:   673:   674:   675:   676:   677:   678:   679:   680:   681:   682:   683: 
  684: 
  685: abstract class HTMLPurifier_AttrTransform
  686: {
  687: 
  688:       689:   690:   691:   692:   693:   694:   695:   696: 
  697:     abstract public function transform($attr, $config, $context);
  698: 
  699:       700:   701:   702:   703:   704: 
  705:     public function prependCSS(&$attr, $css)
  706:     {
  707:         $attr['style'] = isset($attr['style']) ? $attr['style'] : '';
  708:         $attr['style'] = $css . $attr['style'];
  709:     }
  710: 
  711:       712:   713:   714:   715:   716: 
  717:     public function confiscateAttr(&$attr, $key)
  718:     {
  719:         if (!isset($attr[$key])) {
  720:             return null;
  721:         }
  722:         $value = $attr[$key];
  723:         unset($attr[$key]);
  724:         return $value;
  725:     }
  726: }
  727: 
  728: 
  729: 
  730: 
  731: 
  732:   733:   734: 
  735: class HTMLPurifier_AttrTypes
  736: {
  737:       738:   739:   740: 
  741:     protected $info = array();
  742: 
  743:       744:   745:   746: 
  747:     public function __construct()
  748:     {
  749:         
  750:         
  751:         
  752:         
  753:         
  754:         
  755: 
  756:         
  757:         $this->info['Enum']    = new HTMLPurifier_AttrDef_Enum();
  758:         $this->info['Bool']    = new HTMLPurifier_AttrDef_HTML_Bool();
  759: 
  760:         $this->info['CDATA']    = new HTMLPurifier_AttrDef_Text();
  761:         $this->info['ID']       = new HTMLPurifier_AttrDef_HTML_ID();
  762:         $this->info['Length']   = new HTMLPurifier_AttrDef_HTML_Length();
  763:         $this->info['MultiLength'] = new HTMLPurifier_AttrDef_HTML_MultiLength();
  764:         $this->info['NMTOKENS'] = new HTMLPurifier_AttrDef_HTML_Nmtokens();
  765:         $this->info['Pixels']   = new HTMLPurifier_AttrDef_HTML_Pixels();
  766:         $this->info['Text']     = new HTMLPurifier_AttrDef_Text();
  767:         $this->info['URI']      = new HTMLPurifier_AttrDef_URI();
  768:         $this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang();
  769:         $this->info['Color']    = new HTMLPurifier_AttrDef_HTML_Color();
  770:         $this->info['IAlign']   = self::makeEnum('top,middle,bottom,left,right');
  771:         $this->info['LAlign']   = self::makeEnum('top,bottom,left,right');
  772:         $this->info['FrameTarget'] = new HTMLPurifier_AttrDef_HTML_FrameTarget();
  773: 
  774:         
  775:         $this->info['ContentType'] = new HTMLPurifier_AttrDef_Text();
  776:         $this->info['ContentTypes'] = new HTMLPurifier_AttrDef_Text();
  777:         $this->info['Charsets'] = new HTMLPurifier_AttrDef_Text();
  778:         $this->info['Character'] = new HTMLPurifier_AttrDef_Text();
  779: 
  780:         
  781:         $this->info['Class'] = new HTMLPurifier_AttrDef_HTML_Class();
  782: 
  783:         
  784:         
  785:         $this->info['Number']   = new HTMLPurifier_AttrDef_Integer(false, false, true);
  786:     }
  787: 
  788:     private static function makeEnum($in)
  789:     {
  790:         return new HTMLPurifier_AttrDef_Clone(new HTMLPurifier_AttrDef_Enum(explode(',', $in)));
  791:     }
  792: 
  793:       794:   795:   796:   797: 
  798:     public function get($type)
  799:     {
  800:         
  801:         if (strpos($type, '#') !== false) {
  802:             list($type, $string) = explode('#', $type, 2);
  803:         } else {
  804:             $string = '';
  805:         }
  806: 
  807:         if (!isset($this->info[$type])) {
  808:             trigger_error('Cannot retrieve undefined attribute type ' . $type, E_USER_ERROR);
  809:             return;
  810:         }
  811:         return $this->info[$type]->make($string);
  812:     }
  813: 
  814:       815:   816:   817:   818: 
  819:     public function set($type, $impl)
  820:     {
  821:         $this->info[$type] = $impl;
  822:     }
  823: }
  824: 
  825: 
  826: 
  827: 
  828: 
  829:   830:   831:   832:   833: 
  834: class HTMLPurifier_AttrValidator
  835: {
  836: 
  837:       838:   839:   840:   841:   842:   843: 
  844:     public function validateToken($token, $config, $context)
  845:     {
  846:         $definition = $config->getHTMLDefinition();
  847:         $e =& $context->get('ErrorCollector', true);
  848: 
  849:         
  850:         $ok =& $context->get('IDAccumulator', true);
  851:         if (!$ok) {
  852:             $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context);
  853:             $context->register('IDAccumulator', $id_accumulator);
  854:         }
  855: 
  856:         
  857:         $current_token =& $context->get('CurrentToken', true);
  858:         if (!$current_token) {
  859:             $context->register('CurrentToken', $token);
  860:         }
  861: 
  862:         if (!$token instanceof HTMLPurifier_Token_Start &&
  863:             !$token instanceof HTMLPurifier_Token_Empty
  864:         ) {
  865:             return;
  866:         }
  867: 
  868:         
  869:         
  870:         $d_defs = $definition->info_global_attr;
  871: 
  872:         
  873:         $attr = $token->attr;
  874: 
  875:         
  876:         
  877:         foreach ($definition->info_attr_transform_pre as $transform) {
  878:             $attr = $transform->transform($o = $attr, $config, $context);
  879:             if ($e) {
  880:                 if ($attr != $o) {
  881:                     $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
  882:                 }
  883:             }
  884:         }
  885: 
  886:         
  887:         
  888:         foreach ($definition->info[$token->name]->attr_transform_pre as $transform) {
  889:             $attr = $transform->transform($o = $attr, $config, $context);
  890:             if ($e) {
  891:                 if ($attr != $o) {
  892:                     $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
  893:                 }
  894:             }
  895:         }
  896: 
  897:         
  898:         
  899:         
  900:         $defs = $definition->info[$token->name]->attr;
  901: 
  902:         $attr_key = false;
  903:         $context->register('CurrentAttr', $attr_key);
  904: 
  905:         
  906:         
  907:         foreach ($attr as $attr_key => $value) {
  908: 
  909:             
  910:             if (isset($defs[$attr_key])) {
  911:                 
  912:                 if ($defs[$attr_key] === false) {
  913:                     
  914:                     
  915:                     
  916:                     
  917:                     
  918:                     $result = false;
  919:                 } else {
  920:                     
  921:                     $result = $defs[$attr_key]->validate(
  922:                         $value,
  923:                         $config,
  924:                         $context
  925:                     );
  926:                 }
  927:             } elseif (isset($d_defs[$attr_key])) {
  928:                 
  929:                 
  930:                 $result = $d_defs[$attr_key]->validate(
  931:                     $value,
  932:                     $config,
  933:                     $context
  934:                 );
  935:             } else {
  936:                 
  937:                 $result = false;
  938:             }
  939: 
  940:             
  941:             if ($result === false || $result === null) {
  942:                 
  943:                 
  944:                 if ($e) {
  945:                     $e->send(E_ERROR, 'AttrValidator: Attribute removed');
  946:                 }
  947: 
  948:                 
  949:                 unset($attr[$attr_key]);
  950:             } elseif (is_string($result)) {
  951:                 
  952:                 
  953:                 
  954: 
  955:                 
  956:                 $attr[$attr_key] = $result;
  957:             } else {
  958:                 
  959:             }
  960: 
  961:             
  962:             
  963:             
  964:             
  965:             
  966:         }
  967: 
  968:         $context->destroy('CurrentAttr');
  969: 
  970:         
  971: 
  972:         
  973:         foreach ($definition->info_attr_transform_post as $transform) {
  974:             $attr = $transform->transform($o = $attr, $config, $context);
  975:             if ($e) {
  976:                 if ($attr != $o) {
  977:                     $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
  978:                 }
  979:             }
  980:         }
  981: 
  982:         
  983:         foreach ($definition->info[$token->name]->attr_transform_post as $transform) {
  984:             $attr = $transform->transform($o = $attr, $config, $context);
  985:             if ($e) {
  986:                 if ($attr != $o) {
  987:                     $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
  988:                 }
  989:             }
  990:         }
  991: 
  992:         $token->attr = $attr;
  993: 
  994:         
  995:         if (!$current_token) {
  996:             $context->destroy('CurrentToken');
  997:         }
  998: 
  999:     }
 1000: 
 1001: 
 1002: }
 1003: 
 1004: 
 1005: 
 1006: 
 1007: 
 1008: 
 1009: if (!defined('HTMLPURIFIER_PREFIX')) {
 1010:     define('HTMLPURIFIER_PREFIX', dirname(__FILE__) . '/standalone');
 1011:     set_include_path(HTMLPURIFIER_PREFIX . PATH_SEPARATOR . get_include_path());
 1012: }
 1013: 
 1014: 
 1015: 
 1016: if (!defined('PHP_EOL')) {
 1017:     switch (strtoupper(substr(PHP_OS, 0, 3))) {
 1018:         case 'WIN':
 1019:             define('PHP_EOL', "\r\n");
 1020:             break;
 1021:         case 'DAR':
 1022:             define('PHP_EOL', "\r");
 1023:             break;
 1024:         default:
 1025:             define('PHP_EOL', "\n");
 1026:     }
 1027: }
 1028: 
 1029:  1030:  1031:  1032:  1033:  1034:  1035: 
 1036: class HTMLPurifier_Bootstrap
 1037: {
 1038: 
 1039:      1040:  1041:  1042:  1043: 
 1044:     public static function autoload($class)
 1045:     {
 1046:         $file = HTMLPurifier_Bootstrap::getPath($class);
 1047:         if (!$file) {
 1048:             return false;
 1049:         }
 1050:         
 1051:         
 1052:         
 1053:         
 1054:         
 1055:         require_once HTMLPURIFIER_PREFIX . '/' . $file;
 1056:         return true;
 1057:     }
 1058: 
 1059:      1060:  1061:  1062:  1063: 
 1064:     public static function getPath($class)
 1065:     {
 1066:         if (strncmp('HTMLPurifier', $class, 12) !== 0) {
 1067:             return false;
 1068:         }
 1069:         
 1070:         if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) {
 1071:             $code = str_replace('_', '-', substr($class, 22));
 1072:             $file = 'HTMLPurifier/Language/classes/' . $code . '.php';
 1073:         } else {
 1074:             $file = str_replace('_', '/', $class) . '.php';
 1075:         }
 1076:         if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) {
 1077:             return false;
 1078:         }
 1079:         return $file;
 1080:     }
 1081: 
 1082:      1083:  1084: 
 1085:     public static function registerAutoload()
 1086:     {
 1087:         $autoload = array('HTMLPurifier_Bootstrap', 'autoload');
 1088:         if (($funcs = spl_autoload_functions()) === false) {
 1089:             spl_autoload_register($autoload);
 1090:         } elseif (function_exists('spl_autoload_unregister')) {
 1091:             if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
 1092:                 
 1093:                 spl_autoload_register($autoload, true, true);
 1094:             } else {
 1095:                 $buggy  = version_compare(PHP_VERSION, '5.2.11', '<');
 1096:                 $compat = version_compare(PHP_VERSION, '5.1.2', '<=') &&
 1097:                           version_compare(PHP_VERSION, '5.1.0', '>=');
 1098:                 foreach ($funcs as $func) {
 1099:                     if ($buggy && is_array($func)) {
 1100:                         
 1101:                         
 1102:                         $reflector = new ReflectionMethod($func[0], $func[1]);
 1103:                         if (!$reflector->isStatic()) {
 1104:                             throw new Exception(
 1105:                                 'HTML Purifier autoloader registrar is not compatible
 1106:                                 with non-static object methods due to PHP Bug #44144;
 1107:                                 Please do not use HTMLPurifier.autoload.php (or any
 1108:                                 file that includes this file); instead, place the code:
 1109:                                 spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\'))
 1110:                                 after your own autoloaders.'
 1111:                             );
 1112:                         }
 1113:                         
 1114:                         
 1115:                         if ($compat) {
 1116:                             $func = implode('::', $func);
 1117:                         }
 1118:                     }
 1119:                     spl_autoload_unregister($func);
 1120:                 }
 1121:                 spl_autoload_register($autoload);
 1122:                 foreach ($funcs as $func) {
 1123:                     spl_autoload_register($func);
 1124:                 }
 1125:             }
 1126:         }
 1127:     }
 1128: }
 1129: 
 1130: 
 1131: 
 1132: 
 1133: 
 1134:  1135:  1136:  1137: 
 1138: abstract class HTMLPurifier_Definition
 1139: {
 1140: 
 1141:      1142:  1143:  1144: 
 1145:     public $setup = false;
 1146: 
 1147:      1148:  1149:  1150:  1151:  1152:  1153:  1154:  1155:  1156: 
 1157:     public $optimized = null;
 1158: 
 1159:      1160:  1161:  1162: 
 1163:     public $type;
 1164: 
 1165:      1166:  1167:  1168:  1169: 
 1170:     abstract protected function doSetup($config);
 1171: 
 1172:      1173:  1174:  1175: 
 1176:     public function setup($config)
 1177:     {
 1178:         if ($this->setup) {
 1179:             return;
 1180:         }
 1181:         $this->setup = true;
 1182:         $this->doSetup($config);
 1183:     }
 1184: }
 1185: 
 1186: 
 1187: 
 1188: 
 1189: 
 1190:  1191:  1192:  1193: 
 1194: class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
 1195: {
 1196: 
 1197:     public $type = 'CSS';
 1198: 
 1199:      1200:  1201:  1202: 
 1203:     public $info = array();
 1204: 
 1205:      1206:  1207:  1208: 
 1209:     protected function doSetup($config)
 1210:     {
 1211:         $this->info['text-align'] = new HTMLPurifier_AttrDef_Enum(
 1212:             array('left', 'right', 'center', 'justify'),
 1213:             false
 1214:         );
 1215: 
 1216:         $border_style =
 1217:             $this->info['border-bottom-style'] =
 1218:             $this->info['border-right-style'] =
 1219:             $this->info['border-left-style'] =
 1220:             $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum(
 1221:                 array(
 1222:                     'none',
 1223:                     'hidden',
 1224:                     'dotted',
 1225:                     'dashed',
 1226:                     'solid',
 1227:                     'double',
 1228:                     'groove',
 1229:                     'ridge',
 1230:                     'inset',
 1231:                     'outset'
 1232:                 ),
 1233:                 false
 1234:             );
 1235: 
 1236:         $this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style);
 1237: 
 1238:         $this->info['clear'] = new HTMLPurifier_AttrDef_Enum(
 1239:             array('none', 'left', 'right', 'both'),
 1240:             false
 1241:         );
 1242:         $this->info['float'] = new HTMLPurifier_AttrDef_Enum(
 1243:             array('none', 'left', 'right'),
 1244:             false
 1245:         );
 1246:         $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum(
 1247:             array('normal', 'italic', 'oblique'),
 1248:             false
 1249:         );
 1250:         $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum(
 1251:             array('normal', 'small-caps'),
 1252:             false
 1253:         );
 1254: 
 1255:         $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite(
 1256:             array(
 1257:                 new HTMLPurifier_AttrDef_Enum(array('none')),
 1258:                 new HTMLPurifier_AttrDef_CSS_URI()
 1259:             )
 1260:         );
 1261: 
 1262:         $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum(
 1263:             array('inside', 'outside'),
 1264:             false
 1265:         );
 1266:         $this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum(
 1267:             array(
 1268:                 'disc',
 1269:                 'circle',
 1270:                 'square',
 1271:                 'decimal',
 1272:                 'lower-roman',
 1273:                 'upper-roman',
 1274:                 'lower-alpha',
 1275:                 'upper-alpha',
 1276:                 'none'
 1277:             ),
 1278:             false
 1279:         );
 1280:         $this->info['list-style-image'] = $uri_or_none;
 1281: 
 1282:         $this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config);
 1283: 
 1284:         $this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum(
 1285:             array('capitalize', 'uppercase', 'lowercase', 'none'),
 1286:             false
 1287:         );
 1288:         $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color();
 1289: 
 1290:         $this->info['background-image'] = $uri_or_none;
 1291:         $this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum(
 1292:             array('repeat', 'repeat-x', 'repeat-y', 'no-repeat')
 1293:         );
 1294:         $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum(
 1295:             array('scroll', 'fixed')
 1296:         );
 1297:         $this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition();
 1298: 
 1299:         $border_color =
 1300:             $this->info['border-top-color'] =
 1301:             $this->info['border-bottom-color'] =
 1302:             $this->info['border-left-color'] =
 1303:             $this->info['border-right-color'] =
 1304:             $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(
 1305:                 array(
 1306:                     new HTMLPurifier_AttrDef_Enum(array('transparent')),
 1307:                     new HTMLPurifier_AttrDef_CSS_Color()
 1308:                 )
 1309:             );
 1310: 
 1311:         $this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config);
 1312: 
 1313:         $this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color);
 1314: 
 1315:         $border_width =
 1316:             $this->info['border-top-width'] =
 1317:             $this->info['border-bottom-width'] =
 1318:             $this->info['border-left-width'] =
 1319:             $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(
 1320:                 array(
 1321:                     new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')),
 1322:                     new HTMLPurifier_AttrDef_CSS_Length('0') 
 1323:                 )
 1324:             );
 1325: 
 1326:         $this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width);
 1327: 
 1328:         $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
 1329:             array(
 1330:                 new HTMLPurifier_AttrDef_Enum(array('normal')),
 1331:                 new HTMLPurifier_AttrDef_CSS_Length()
 1332:             )
 1333:         );
 1334: 
 1335:         $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
 1336:             array(
 1337:                 new HTMLPurifier_AttrDef_Enum(array('normal')),
 1338:                 new HTMLPurifier_AttrDef_CSS_Length()
 1339:             )
 1340:         );
 1341: 
 1342:         $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(
 1343:             array(
 1344:                 new HTMLPurifier_AttrDef_Enum(
 1345:                     array(
 1346:                         'xx-small',
 1347:                         'x-small',
 1348:                         'small',
 1349:                         'medium',
 1350:                         'large',
 1351:                         'x-large',
 1352:                         'xx-large',
 1353:                         'larger',
 1354:                         'smaller'
 1355:                     )
 1356:                 ),
 1357:                 new HTMLPurifier_AttrDef_CSS_Percentage(),
 1358:                 new HTMLPurifier_AttrDef_CSS_Length()
 1359:             )
 1360:         );
 1361: 
 1362:         $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(
 1363:             array(
 1364:                 new HTMLPurifier_AttrDef_Enum(array('normal')),
 1365:                 new HTMLPurifier_AttrDef_CSS_Number(true), 
 1366:                 new HTMLPurifier_AttrDef_CSS_Length('0'),
 1367:                 new HTMLPurifier_AttrDef_CSS_Percentage(true)
 1368:             )
 1369:         );
 1370: 
 1371:         $margin =
 1372:             $this->info['margin-top'] =
 1373:             $this->info['margin-bottom'] =
 1374:             $this->info['margin-left'] =
 1375:             $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
 1376:                 array(
 1377:                     new HTMLPurifier_AttrDef_CSS_Length(),
 1378:                     new HTMLPurifier_AttrDef_CSS_Percentage(),
 1379:                     new HTMLPurifier_AttrDef_Enum(array('auto'))
 1380:                 )
 1381:             );
 1382: 
 1383:         $this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin);
 1384: 
 1385:         
 1386:         $padding =
 1387:             $this->info['padding-top'] =
 1388:             $this->info['padding-bottom'] =
 1389:             $this->info['padding-left'] =
 1390:             $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
 1391:                 array(
 1392:                     new HTMLPurifier_AttrDef_CSS_Length('0'),
 1393:                     new HTMLPurifier_AttrDef_CSS_Percentage(true)
 1394:                 )
 1395:             );
 1396: 
 1397:         $this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding);
 1398: 
 1399:         $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(
 1400:             array(
 1401:                 new HTMLPurifier_AttrDef_CSS_Length(),
 1402:                 new HTMLPurifier_AttrDef_CSS_Percentage()
 1403:             )
 1404:         );
 1405: 
 1406:         $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(
 1407:             array(
 1408:                 new HTMLPurifier_AttrDef_CSS_Length('0'),
 1409:                 new HTMLPurifier_AttrDef_CSS_Percentage(true),
 1410:                 new HTMLPurifier_AttrDef_Enum(array('auto'))
 1411:             )
 1412:         );
 1413:         $max = $config->get('CSS.MaxImgLength');
 1414: 
 1415:         $this->info['width'] =
 1416:         $this->info['height'] =
 1417:             $max === null ?
 1418:                 $trusted_wh :
 1419:                 new HTMLPurifier_AttrDef_Switch(
 1420:                     'img',
 1421:                     
 1422:                     new HTMLPurifier_AttrDef_CSS_Composite(
 1423:                         array(
 1424:                             new HTMLPurifier_AttrDef_CSS_Length('0', $max),
 1425:                             new HTMLPurifier_AttrDef_Enum(array('auto'))
 1426:                         )
 1427:                     ),
 1428:                     
 1429:                     $trusted_wh
 1430:                 );
 1431: 
 1432:         $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration();
 1433: 
 1434:         $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily();
 1435: 
 1436:         
 1437:         $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum(
 1438:             array(
 1439:                 'normal',
 1440:                 'bold',
 1441:                 'bolder',
 1442:                 'lighter',
 1443:                 '100',
 1444:                 '200',
 1445:                 '300',
 1446:                 '400',
 1447:                 '500',
 1448:                 '600',
 1449:                 '700',
 1450:                 '800',
 1451:                 '900'
 1452:             ),
 1453:             false
 1454:         );
 1455: 
 1456:         
 1457:         
 1458:         $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config);
 1459: 
 1460:         
 1461:         $this->info['border'] =
 1462:         $this->info['border-bottom'] =
 1463:         $this->info['border-top'] =
 1464:         $this->info['border-left'] =
 1465:         $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config);
 1466: 
 1467:         $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(
 1468:             array('collapse', 'separate')
 1469:         );
 1470: 
 1471:         $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(
 1472:             array('top', 'bottom')
 1473:         );
 1474: 
 1475:         $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(
 1476:             array('auto', 'fixed')
 1477:         );
 1478: 
 1479:         $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(
 1480:             array(
 1481:                 new HTMLPurifier_AttrDef_Enum(
 1482:                     array(
 1483:                         'baseline',
 1484:                         'sub',
 1485:                         'super',
 1486:                         'top',
 1487:                         'text-top',
 1488:                         'middle',
 1489:                         'bottom',
 1490:                         'text-bottom'
 1491:                     )
 1492:                 ),
 1493:                 new HTMLPurifier_AttrDef_CSS_Length(),
 1494:                 new HTMLPurifier_AttrDef_CSS_Percentage()
 1495:             )
 1496:         );
 1497: 
 1498:         $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2);
 1499: 
 1500:         
 1501:         
 1502:         $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(
 1503:             array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line')
 1504:         );
 1505: 
 1506:         if ($config->get('CSS.Proprietary')) {
 1507:             $this->doSetupProprietary($config);
 1508:         }
 1509: 
 1510:         if ($config->get('CSS.AllowTricky')) {
 1511:             $this->doSetupTricky($config);
 1512:         }
 1513: 
 1514:         if ($config->get('CSS.Trusted')) {
 1515:             $this->doSetupTrusted($config);
 1516:         }
 1517: 
 1518:         $allow_important = $config->get('CSS.AllowImportant');
 1519:         
 1520:         foreach ($this->info as $k => $v) {
 1521:             $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important);
 1522:         }
 1523: 
 1524:         $this->setupConfigStuff($config);
 1525:     }
 1526: 
 1527:      1528:  1529: 
 1530:     protected function doSetupProprietary($config)
 1531:     {
 1532:         
 1533:         $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
 1534:         $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color();
 1535:         $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
 1536:         $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color();
 1537:         $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color();
 1538:         $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
 1539: 
 1540:         
 1541:         $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
 1542:         $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
 1543:         $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
 1544: 
 1545:         
 1546:         $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter();
 1547: 
 1548:         
 1549:         $this->info['page-break-after'] =
 1550:         $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum(
 1551:             array(
 1552:                 'auto',
 1553:                 'always',
 1554:                 'avoid',
 1555:                 'left',
 1556:                 'right'
 1557:             )
 1558:         );
 1559:         $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid'));
 1560: 
 1561:     }
 1562: 
 1563:      1564:  1565: 
 1566:     protected function doSetupTricky($config)
 1567:     {
 1568:         $this->info['display'] = new HTMLPurifier_AttrDef_Enum(
 1569:             array(
 1570:                 'inline',
 1571:                 'block',
 1572:                 'list-item',
 1573:                 'run-in',
 1574:                 'compact',
 1575:                 'marker',
 1576:                 'table',
 1577:                 'inline-block',
 1578:                 'inline-table',
 1579:                 'table-row-group',
 1580:                 'table-header-group',
 1581:                 'table-footer-group',
 1582:                 'table-row',
 1583:                 'table-column-group',
 1584:                 'table-column',
 1585:                 'table-cell',
 1586:                 'table-caption',
 1587:                 'none'
 1588:             )
 1589:         );
 1590:         $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(
 1591:             array('visible', 'hidden', 'collapse')
 1592:         );
 1593:         $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll'));
 1594:     }
 1595: 
 1596:      1597:  1598: 
 1599:     protected function doSetupTrusted($config)
 1600:     {
 1601:         $this->info['position'] = new HTMLPurifier_AttrDef_Enum(
 1602:             array('static', 'relative', 'absolute', 'fixed')
 1603:         );
 1604:         $this->info['top'] =
 1605:         $this->info['left'] =
 1606:         $this->info['right'] =
 1607:         $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite(
 1608:             array(
 1609:                 new HTMLPurifier_AttrDef_CSS_Length(),
 1610:                 new HTMLPurifier_AttrDef_CSS_Percentage(),
 1611:                 new HTMLPurifier_AttrDef_Enum(array('auto')),
 1612:             )
 1613:         );
 1614:         $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite(
 1615:             array(
 1616:                 new HTMLPurifier_AttrDef_Integer(),
 1617:                 new HTMLPurifier_AttrDef_Enum(array('auto')),
 1618:             )
 1619:         );
 1620:     }
 1621: 
 1622:      1623:  1624:  1625:  1626:  1627:  1628: 
 1629:     protected function setupConfigStuff($config)
 1630:     {
 1631:         
 1632:         $support = "(for information on implementing this, see the " .
 1633:             "support forums) ";
 1634:         $allowed_properties = $config->get('CSS.AllowedProperties');
 1635:         if ($allowed_properties !== null) {
 1636:             foreach ($this->info as $name => $d) {
 1637:                 if (!isset($allowed_properties[$name])) {
 1638:                     unset($this->info[$name]);
 1639:                 }
 1640:                 unset($allowed_properties[$name]);
 1641:             }
 1642:             
 1643:             foreach ($allowed_properties as $name => $d) {
 1644:                 
 1645:                 $name = htmlspecialchars($name);
 1646:                 trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING);
 1647:             }
 1648:         }
 1649: 
 1650:         $forbidden_properties = $config->get('CSS.ForbiddenProperties');
 1651:         if ($forbidden_properties !== null) {
 1652:             foreach ($this->info as $name => $d) {
 1653:                 if (isset($forbidden_properties[$name])) {
 1654:                     unset($this->info[$name]);
 1655:                 }
 1656:             }
 1657:         }
 1658:     }
 1659: }
 1660: 
 1661: 
 1662: 
 1663: 
 1664: 
 1665:  1666:  1667: 
 1668: abstract class HTMLPurifier_ChildDef
 1669: {
 1670:      1671:  1672:  1673:  1674: 
 1675:     public $type;
 1676: 
 1677:      1678:  1679:  1680:  1681:  1682:  1683: 
 1684:     public $allow_empty;
 1685: 
 1686:      1687:  1688:  1689: 
 1690:     public $elements = array();
 1691: 
 1692:      1693:  1694:  1695:  1696:  1697: 
 1698:     public function getAllowedElements($config)
 1699:     {
 1700:         return $this->elements;
 1701:     }
 1702: 
 1703:      1704:  1705:  1706:  1707:  1708:  1709:  1710: 
 1711:     abstract public function validateChildren($children, $config, $context);
 1712: }
 1713: 
 1714: 
 1715: 
 1716: 
 1717: 
 1718:  1719:  1720:  1721:  1722:  1723:  1724:  1725:  1726:  1727:  1728:  1729:  1730:  1731: 
 1732: class HTMLPurifier_Config
 1733: {
 1734: 
 1735:      1736:  1737:  1738: 
 1739:     public $version = '4.6.0';
 1740: 
 1741:      1742:  1743:  1744:  1745: 
 1746:     public $autoFinalize = true;
 1747: 
 1748:     
 1749: 
 1750:      1751:  1752:  1753:  1754: 
 1755:     protected $serials = array();
 1756: 
 1757:      1758:  1759:  1760: 
 1761:     protected $serial;
 1762: 
 1763:      1764:  1765:  1766: 
 1767:     protected $parser = null;
 1768: 
 1769:      1770:  1771:  1772:  1773:  1774: 
 1775:     public $def;
 1776: 
 1777:      1778:  1779:  1780: 
 1781:     protected $definitions;
 1782: 
 1783:      1784:  1785:  1786: 
 1787:     protected $finalized = false;
 1788: 
 1789:      1790:  1791:  1792: 
 1793:     protected $plist;
 1794: 
 1795:      1796:  1797:  1798: 
 1799:     private $aliasMode;
 1800: 
 1801:      1802:  1803:  1804:  1805:  1806: 
 1807:     public $chatty = true;
 1808: 
 1809:      1810:  1811:  1812: 
 1813:     private $lock;
 1814: 
 1815:      1816:  1817:  1818:  1819:  1820: 
 1821:     public function __construct($definition, $parent = null)
 1822:     {
 1823:         $parent = $parent ? $parent : $definition->defaultPlist;
 1824:         $this->plist = new HTMLPurifier_PropertyList($parent);
 1825:         $this->def = $definition; 
 1826:         $this->parser = new HTMLPurifier_VarParser_Flexible();
 1827:     }
 1828: 
 1829:      1830:  1831:  1832:  1833:  1834:  1835:  1836:  1837: 
 1838:     public static function create($config, $schema = null)
 1839:     {
 1840:         if ($config instanceof HTMLPurifier_Config) {
 1841:             
 1842:             return $config;
 1843:         }
 1844:         if (!$schema) {
 1845:             $ret = HTMLPurifier_Config::createDefault();
 1846:         } else {
 1847:             $ret = new HTMLPurifier_Config($schema);
 1848:         }
 1849:         if (is_string($config)) {
 1850:             $ret->loadIni($config);
 1851:         } elseif (is_array($config)) $ret->loadArray($config);
 1852:         return $ret;
 1853:     }
 1854: 
 1855:      1856:  1857:  1858:  1859: 
 1860:     public static function inherit(HTMLPurifier_Config $config)
 1861:     {
 1862:         return new HTMLPurifier_Config($config->def, $config->plist);
 1863:     }
 1864: 
 1865:      1866:  1867:  1868: 
 1869:     public static function createDefault()
 1870:     {
 1871:         $definition = HTMLPurifier_ConfigSchema::instance();
 1872:         $config = new HTMLPurifier_Config($definition);
 1873:         return $config;
 1874:     }
 1875: 
 1876:      1877:  1878:  1879:  1880:  1881:  1882:  1883: 
 1884:     public function get($key, $a = null)
 1885:     {
 1886:         if ($a !== null) {
 1887:             $this->triggerError(
 1888:                 "Using deprecated API: use \$config->get('$key.$a') instead",
 1889:                 E_USER_WARNING
 1890:             );
 1891:             $key = "$key.$a";
 1892:         }
 1893:         if (!$this->finalized) {
 1894:             $this->autoFinalize();
 1895:         }
 1896:         if (!isset($this->def->info[$key])) {
 1897:             
 1898:             $this->triggerError(
 1899:                 'Cannot retrieve value of undefined directive ' . htmlspecialchars($key),
 1900:                 E_USER_WARNING
 1901:             );
 1902:             return;
 1903:         }
 1904:         if (isset($this->def->info[$key]->isAlias)) {
 1905:             $d = $this->def->info[$key];
 1906:             $this->triggerError(
 1907:                 'Cannot get value from aliased directive, use real name ' . $d->key,
 1908:                 E_USER_ERROR
 1909:             );
 1910:             return;
 1911:         }
 1912:         if ($this->lock) {
 1913:             list($ns) = explode('.', $key);
 1914:             if ($ns !== $this->lock) {
 1915:                 $this->triggerError(
 1916:                     'Cannot get value of namespace ' . $ns . ' when lock for ' .
 1917:                     $this->lock .
 1918:                     ' is active, this probably indicates a Definition setup method ' .
 1919:                     'is accessing directives that are not within its namespace',
 1920:                     E_USER_ERROR
 1921:                 );
 1922:                 return;
 1923:             }
 1924:         }
 1925:         return $this->plist->get($key);
 1926:     }
 1927: 
 1928:      1929:  1930:  1931:  1932:  1933:  1934: 
 1935:     public function getBatch($namespace)
 1936:     {
 1937:         if (!$this->finalized) {
 1938:             $this->autoFinalize();
 1939:         }
 1940:         $full = $this->getAll();
 1941:         if (!isset($full[$namespace])) {
 1942:             $this->triggerError(
 1943:                 'Cannot retrieve undefined namespace ' .
 1944:                 htmlspecialchars($namespace),
 1945:                 E_USER_WARNING
 1946:             );
 1947:             return;
 1948:         }
 1949:         return $full[$namespace];
 1950:     }
 1951: 
 1952:      1953:  1954:  1955:  1956:  1957:  1958:  1959:  1960:  1961: 
 1962:     public function getBatchSerial($namespace)
 1963:     {
 1964:         if (empty($this->serials[$namespace])) {
 1965:             $batch = $this->getBatch($namespace);
 1966:             unset($batch['DefinitionRev']);
 1967:             $this->serials[$namespace] = sha1(serialize($batch));
 1968:         }
 1969:         return $this->serials[$namespace];
 1970:     }
 1971: 
 1972:      1973:  1974:  1975:  1976:  1977: 
 1978:     public function getSerial()
 1979:     {
 1980:         if (empty($this->serial)) {
 1981:             $this->serial = sha1(serialize($this->getAll()));
 1982:         }
 1983:         return $this->serial;
 1984:     }
 1985: 
 1986:      1987:  1988:  1989:  1990: 
 1991:     public function getAll()
 1992:     {
 1993:         if (!$this->finalized) {
 1994:             $this->autoFinalize();
 1995:         }
 1996:         $ret = array();
 1997:         foreach ($this->plist->squash() as $name => $value) {
 1998:             list($ns, $key) = explode('.', $name, 2);
 1999:             $ret[$ns][$key] = $value;
 2000:         }
 2001:         return $ret;
 2002:     }
 2003: 
 2004:      2005:  2006:  2007:  2008:  2009:  2010: 
 2011:     public function set($key, $value, $a = null)
 2012:     {
 2013:         if (strpos($key, '.') === false) {
 2014:             $namespace = $key;
 2015:             $directive = $value;
 2016:             $value = $a;
 2017:             $key = "$key.$directive";
 2018:             $this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE);
 2019:         } else {
 2020:             list($namespace) = explode('.', $key);
 2021:         }
 2022:         if ($this->isFinalized('Cannot set directive after finalization')) {
 2023:             return;
 2024:         }
 2025:         if (!isset($this->def->info[$key])) {
 2026:             $this->triggerError(
 2027:                 'Cannot set undefined directive ' . htmlspecialchars($key) . ' to value',
 2028:                 E_USER_WARNING
 2029:             );
 2030:             return;
 2031:         }
 2032:         $def = $this->def->info[$key];
 2033: 
 2034:         if (isset($def->isAlias)) {
 2035:             if ($this->aliasMode) {
 2036:                 $this->triggerError(
 2037:                     'Double-aliases not allowed, please fix '.
 2038:                     'ConfigSchema bug with' . $key,
 2039:                     E_USER_ERROR
 2040:                 );
 2041:                 return;
 2042:             }
 2043:             $this->aliasMode = true;
 2044:             $this->set($def->key, $value);
 2045:             $this->aliasMode = false;
 2046:             $this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE);
 2047:             return;
 2048:         }
 2049: 
 2050:         
 2051:         
 2052:         $rtype = is_int($def) ? $def : $def->type;
 2053:         if ($rtype < 0) {
 2054:             $type = -$rtype;
 2055:             $allow_null = true;
 2056:         } else {
 2057:             $type = $rtype;
 2058:             $allow_null = isset($def->allow_null);
 2059:         }
 2060: 
 2061:         try {
 2062:             $value = $this->parser->parse($value, $type, $allow_null);
 2063:         } catch (HTMLPurifier_VarParserException $e) {
 2064:             $this->triggerError(
 2065:                 'Value for ' . $key . ' is of invalid type, should be ' .
 2066:                 HTMLPurifier_VarParser::getTypeName($type),
 2067:                 E_USER_WARNING
 2068:             );
 2069:             return;
 2070:         }
 2071:         if (is_string($value) && is_object($def)) {
 2072:             
 2073:             if (isset($def->aliases[$value])) {
 2074:                 $value = $def->aliases[$value];
 2075:             }
 2076:             
 2077:             if (isset($def->allowed) && !isset($def->allowed[$value])) {
 2078:                 $this->triggerError(
 2079:                     'Value not supported, valid values are: ' .
 2080:                     $this->_listify($def->allowed),
 2081:                     E_USER_WARNING
 2082:                 );
 2083:                 return;
 2084:             }
 2085:         }
 2086:         $this->plist->set($key, $value);
 2087: 
 2088:         
 2089:         
 2090:         
 2091:         if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') {
 2092:             $this->definitions[$namespace] = null;
 2093:         }
 2094: 
 2095:         $this->serials[$namespace] = false;
 2096:     }
 2097: 
 2098:      2099:  2100:  2101:  2102:  2103:  2104: 
 2105:     private function _listify($lookup)
 2106:     {
 2107:         $list = array();
 2108:         foreach ($lookup as $name => $b) {
 2109:             $list[] = $name;
 2110:         }
 2111:         return implode(', ', $list);
 2112:     }
 2113: 
 2114:      2115:  2116:  2117:  2118:  2119:  2120:  2121:  2122:  2123:  2124:  2125:  2126:  2127: 
 2128:     public function getHTMLDefinition($raw = false, $optimized = false)
 2129:     {
 2130:         return $this->getDefinition('HTML', $raw, $optimized);
 2131:     }
 2132: 
 2133:      2134:  2135:  2136:  2137:  2138:  2139:  2140:  2141:  2142:  2143:  2144:  2145:  2146: 
 2147:     public function getCSSDefinition($raw = false, $optimized = false)
 2148:     {
 2149:         return $this->getDefinition('CSS', $raw, $optimized);
 2150:     }
 2151: 
 2152:      2153:  2154:  2155:  2156:  2157:  2158:  2159:  2160:  2161:  2162:  2163:  2164:  2165: 
 2166:     public function getURIDefinition($raw = false, $optimized = false)
 2167:     {
 2168:         return $this->getDefinition('URI', $raw, $optimized);
 2169:     }
 2170: 
 2171:      2172:  2173:  2174:  2175:  2176:  2177:  2178:  2179:  2180:  2181:  2182:  2183:  2184:  2185:  2186:  2187: 
 2188:     public function getDefinition($type, $raw = false, $optimized = false)
 2189:     {
 2190:         if ($optimized && !$raw) {
 2191:             throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false");
 2192:         }
 2193:         if (!$this->finalized) {
 2194:             $this->autoFinalize();
 2195:         }
 2196:         
 2197:         $lock = $this->lock;
 2198:         $this->lock = null;
 2199:         $factory = HTMLPurifier_DefinitionCacheFactory::instance();
 2200:         $cache = $factory->create($type, $this);
 2201:         $this->lock = $lock;
 2202:         if (!$raw) {
 2203:             
 2204:             
 2205:             
 2206:             if (!empty($this->definitions[$type])) {
 2207:                 $def = $this->definitions[$type];
 2208:                 
 2209:                 if ($def->setup) {
 2210:                     return $def;
 2211:                 } else {
 2212:                     $def->setup($this);
 2213:                     if ($def->optimized) {
 2214:                         $cache->add($def, $this);
 2215:                     }
 2216:                     return $def;
 2217:                 }
 2218:             }
 2219:             
 2220:             $def = $cache->get($this);
 2221:             if ($def) {
 2222:                 
 2223:                 $this->definitions[$type] = $def;
 2224:                 return $def;
 2225:             }
 2226:             
 2227:             $def = $this->initDefinition($type);
 2228:             
 2229:             $this->lock = $type;
 2230:             $def->setup($this);
 2231:             $this->lock = null;
 2232:             
 2233:             $cache->add($def, $this);
 2234:             
 2235:             return $def;
 2236:         } else {
 2237:             
 2238:             
 2239:             
 2240:             $def = null;
 2241:             if ($optimized) {
 2242:                 if (is_null($this->get($type . '.DefinitionID'))) {
 2243:                     
 2244:                     throw new HTMLPurifier_Exception(
 2245:                         "Cannot retrieve raw version without specifying %$type.DefinitionID"
 2246:                     );
 2247:                 }
 2248:             }
 2249:             if (!empty($this->definitions[$type])) {
 2250:                 $def = $this->definitions[$type];
 2251:                 if ($def->setup && !$optimized) {
 2252:                     $extra = $this->chatty ?
 2253:                         " (try moving this code block earlier in your initialization)" :
 2254:                         "";
 2255:                     throw new HTMLPurifier_Exception(
 2256:                         "Cannot retrieve raw definition after it has already been setup" .
 2257:                         $extra
 2258:                     );
 2259:                 }
 2260:                 if ($def->optimized === null) {
 2261:                     $extra = $this->chatty ? " (try flushing your cache)" : "";
 2262:                     throw new HTMLPurifier_Exception(
 2263:                         "Optimization status of definition is unknown" . $extra
 2264:                     );
 2265:                 }
 2266:                 if ($def->optimized !== $optimized) {
 2267:                     $msg = $optimized ? "optimized" : "unoptimized";
 2268:                     $extra = $this->chatty ?
 2269:                         " (this backtrace is for the first inconsistent call, which was for a $msg raw definition)"
 2270:                         : "";
 2271:                     throw new HTMLPurifier_Exception(
 2272:                         "Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra
 2273:                     );
 2274:                 }
 2275:             }
 2276:             
 2277:             if ($def) {
 2278:                 if ($def->setup) {
 2279:                     
 2280:                     return null;
 2281:                 } else {
 2282:                     return $def;
 2283:                 }
 2284:             }
 2285:             
 2286:             
 2287:             
 2288:             
 2289:             
 2290:             if ($optimized) {
 2291:                 
 2292:                 
 2293:                 
 2294:                 $def = $cache->get($this);
 2295:                 if ($def) {
 2296:                     
 2297:                     
 2298:                     $this->definitions[$type] = $def;
 2299:                     return null;
 2300:                 }
 2301:             }
 2302:             
 2303:             if (!$optimized) {
 2304:                 if (!is_null($this->get($type . '.DefinitionID'))) {
 2305:                     if ($this->chatty) {
 2306:                         $this->triggerError(
 2307:                             'Due to a documentation error in previous version of HTML Purifier, your ' .
 2308:                             'definitions are not being cached.  If this is OK, you can remove the ' .
 2309:                             '%$type.DefinitionRev and %$type.DefinitionID declaration.  Otherwise, ' .
 2310:                             'modify your code to use maybeGetRawDefinition, and test if the returned ' .
 2311:                             'value is null before making any edits (if it is null, that means that a ' .
 2312:                             'cached version is available, and no raw operations are necessary).  See ' .
 2313:                             '<a href="http://htmlpurifier.org/docs/enduser-customize.html#optimized">' .
 2314:                             'Customize</a> for more details',
 2315:                             E_USER_WARNING
 2316:                         );
 2317:                     } else {
 2318:                         $this->triggerError(
 2319:                             "Useless DefinitionID declaration",
 2320:                             E_USER_WARNING
 2321:                         );
 2322:                     }
 2323:                 }
 2324:             }
 2325:             
 2326:             $def = $this->initDefinition($type);
 2327:             $def->optimized = $optimized;
 2328:             return $def;
 2329:         }
 2330:         throw new HTMLPurifier_Exception("The impossible happened!");
 2331:     }
 2332: 
 2333:      2334:  2335:  2336:  2337:  2338:  2339:  2340: 
 2341:     private function initDefinition($type)
 2342:     {
 2343:         
 2344:         if ($type == 'HTML') {
 2345:             $def = new HTMLPurifier_HTMLDefinition();
 2346:         } elseif ($type == 'CSS') {
 2347:             $def = new HTMLPurifier_CSSDefinition();
 2348:         } elseif ($type == 'URI') {
 2349:             $def = new HTMLPurifier_URIDefinition();
 2350:         } else {
 2351:             throw new HTMLPurifier_Exception(
 2352:                 "Definition of $type type not supported"
 2353:             );
 2354:         }
 2355:         $this->definitions[$type] = $def;
 2356:         return $def;
 2357:     }
 2358: 
 2359:     public function maybeGetRawDefinition($name)
 2360:     {
 2361:         return $this->getDefinition($name, true, true);
 2362:     }
 2363: 
 2364:     public function maybeGetRawHTMLDefinition()
 2365:     {
 2366:         return $this->getDefinition('HTML', true, true);
 2367:     }
 2368: 
 2369:     public function maybeGetRawCSSDefinition()
 2370:     {
 2371:         return $this->getDefinition('CSS', true, true);
 2372:     }
 2373: 
 2374:     public function maybeGetRawURIDefinition()
 2375:     {
 2376:         return $this->getDefinition('URI', true, true);
 2377:     }
 2378: 
 2379:      2380:  2381:  2382:  2383:  2384: 
 2385:     public function loadArray($config_array)
 2386:     {
 2387:         if ($this->isFinalized('Cannot load directives after finalization')) {
 2388:             return;
 2389:         }
 2390:         foreach ($config_array as $key => $value) {
 2391:             $key = str_replace('_', '.', $key);
 2392:             if (strpos($key, '.') !== false) {
 2393:                 $this->set($key, $value);
 2394:             } else {
 2395:                 $namespace = $key;
 2396:                 $namespace_values = $value;
 2397:                 foreach ($namespace_values as $directive => $value2) {
 2398:                     $this->set($namespace .'.'. $directive, $value2);
 2399:                 }
 2400:             }
 2401:         }
 2402:     }
 2403: 
 2404:      2405:  2406:  2407:  2408:  2409:  2410:  2411:  2412:  2413: 
 2414:     public static function getAllowedDirectivesForForm($allowed, $schema = null)
 2415:     {
 2416:         if (!$schema) {
 2417:             $schema = HTMLPurifier_ConfigSchema::instance();
 2418:         }
 2419:         if ($allowed !== true) {
 2420:             if (is_string($allowed)) {
 2421:                 $allowed = array($allowed);
 2422:             }
 2423:             $allowed_ns = array();
 2424:             $allowed_directives = array();
 2425:             $blacklisted_directives = array();
 2426:             foreach ($allowed as $ns_or_directive) {
 2427:                 if (strpos($ns_or_directive, '.') !== false) {
 2428:                     
 2429:                     if ($ns_or_directive[0] == '-') {
 2430:                         $blacklisted_directives[substr($ns_or_directive, 1)] = true;
 2431:                     } else {
 2432:                         $allowed_directives[$ns_or_directive] = true;
 2433:                     }
 2434:                 } else {
 2435:                     
 2436:                     $allowed_ns[$ns_or_directive] = true;
 2437:                 }
 2438:             }
 2439:         }
 2440:         $ret = array();
 2441:         foreach ($schema->info as $key => $def) {
 2442:             list($ns, $directive) = explode('.', $key, 2);
 2443:             if ($allowed !== true) {
 2444:                 if (isset($blacklisted_directives["$ns.$directive"])) {
 2445:                     continue;
 2446:                 }
 2447:                 if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) {
 2448:                     continue;
 2449:                 }
 2450:             }
 2451:             if (isset($def->isAlias)) {
 2452:                 continue;
 2453:             }
 2454:             if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') {
 2455:                 continue;
 2456:             }
 2457:             $ret[] = array($ns, $directive);
 2458:         }
 2459:         return $ret;
 2460:     }
 2461: 
 2462:      2463:  2464:  2465:  2466:  2467:  2468:  2469:  2470:  2471:  2472:  2473: 
 2474:     public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
 2475:     {
 2476:         $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema);
 2477:         $config = HTMLPurifier_Config::create($ret, $schema);
 2478:         return $config;
 2479:     }
 2480: 
 2481:      2482:  2483:  2484:  2485:  2486:  2487:  2488: 
 2489:     public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true)
 2490:     {
 2491:          $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def);
 2492:          $this->loadArray($ret);
 2493:     }
 2494: 
 2495:      2496:  2497:  2498:  2499:  2500:  2501:  2502:  2503:  2504:  2505:  2506: 
 2507:     public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
 2508:     {
 2509:         if ($index !== false) {
 2510:             $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
 2511:         }
 2512:         $mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc();
 2513: 
 2514:         $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema);
 2515:         $ret = array();
 2516:         foreach ($allowed as $key) {
 2517:             list($ns, $directive) = $key;
 2518:             $skey = "$ns.$directive";
 2519:             if (!empty($array["Null_$skey"])) {
 2520:                 $ret[$ns][$directive] = null;
 2521:                 continue;
 2522:             }
 2523:             if (!isset($array[$skey])) {
 2524:                 continue;
 2525:             }
 2526:             $value = $mq ? stripslashes($array[$skey]) : $array[$skey];
 2527:             $ret[$ns][$directive] = $value;
 2528:         }
 2529:         return $ret;
 2530:     }
 2531: 
 2532:      2533:  2534:  2535:  2536: 
 2537:     public function loadIni($filename)
 2538:     {
 2539:         if ($this->isFinalized('Cannot load directives after finalization')) {
 2540:             return;
 2541:         }
 2542:         $array = parse_ini_file($filename, true);
 2543:         $this->loadArray($array);
 2544:     }
 2545: 
 2546:      2547:  2548:  2549:  2550:  2551:  2552: 
 2553:     public function isFinalized($error = false)
 2554:     {
 2555:         if ($this->finalized && $error) {
 2556:             $this->triggerError($error, E_USER_ERROR);
 2557:         }
 2558:         return $this->finalized;
 2559:     }
 2560: 
 2561:      2562:  2563:  2564: 
 2565:     public function autoFinalize()
 2566:     {
 2567:         if ($this->autoFinalize) {
 2568:             $this->finalize();
 2569:         } else {
 2570:             $this->plist->squash(true);
 2571:         }
 2572:     }
 2573: 
 2574:      2575:  2576: 
 2577:     public function finalize()
 2578:     {
 2579:         $this->finalized = true;
 2580:         $this->parser = null;
 2581:     }
 2582: 
 2583:      2584:  2585:  2586:  2587:  2588:  2589: 
 2590:     protected function triggerError($msg, $no)
 2591:     {
 2592:         
 2593:         $extra = '';
 2594:         if ($this->chatty) {
 2595:             $trace = debug_backtrace();
 2596:             
 2597:             for ($i = 0, $c = count($trace); $i < $c - 1; $i++) {
 2598:                 
 2599:                 if ($trace[$i + 1]['class'] === 'HTMLPurifier_Config') {
 2600:                     continue;
 2601:                 }
 2602:                 $frame = $trace[$i];
 2603:                 $extra = " invoked on line {$frame['line']} in file {$frame['file']}";
 2604:                 break;
 2605:             }
 2606:         }
 2607:         trigger_error($msg . $extra, $no);
 2608:     }
 2609: 
 2610:      2611:  2612:  2613:  2614:  2615: 
 2616:     public function serialize()
 2617:     {
 2618:         $this->getDefinition('HTML');
 2619:         $this->getDefinition('CSS');
 2620:         $this->getDefinition('URI');
 2621:         return serialize($this);
 2622:     }
 2623: 
 2624: }
 2625: 
 2626: 
 2627: 
 2628: 
 2629: 
 2630:  2631:  2632: 
 2633: class HTMLPurifier_ConfigSchema
 2634: {
 2635:      2636:  2637:  2638:  2639: 
 2640:     public $defaults = array();
 2641: 
 2642:      2643:  2644:  2645: 
 2646:     public $defaultPlist;
 2647: 
 2648:      2649:  2650:  2651:  2652:  2653:  2654:  2655:  2656:  2657:  2658:  2659:  2660:  2661:  2662:  2663:  2664:  2665:  2666:  2667:  2668:  2669:  2670:  2671:  2672:  2673:  2674:  2675:  2676:  2677:  2678: 
 2679:     public $info = array();
 2680: 
 2681:      2682:  2683:  2684: 
 2685:     protected static $singleton;
 2686: 
 2687:     public function __construct()
 2688:     {
 2689:         $this->defaultPlist = new HTMLPurifier_PropertyList();
 2690:     }
 2691: 
 2692:      2693:  2694:  2695: 
 2696:     public static function makeFromSerial()
 2697:     {
 2698:         $contents = file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser');
 2699:         $r = unserialize($contents);
 2700:         if (!$r) {
 2701:             $hash = sha1($contents);
 2702:             trigger_error("Unserialization of configuration schema failed, sha1 of file was $hash", E_USER_ERROR);
 2703:         }
 2704:         return $r;
 2705:     }
 2706: 
 2707:      2708:  2709:  2710:  2711: 
 2712:     public static function instance($prototype = null)
 2713:     {
 2714:         if ($prototype !== null) {
 2715:             HTMLPurifier_ConfigSchema::$singleton = $prototype;
 2716:         } elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) {
 2717:             HTMLPurifier_ConfigSchema::$singleton = HTMLPurifier_ConfigSchema::makeFromSerial();
 2718:         }
 2719:         return HTMLPurifier_ConfigSchema::$singleton;
 2720:     }
 2721: 
 2722:      2723:  2724:  2725:  2726:  2727:  2728:  2729:  2730:  2731:  2732: 
 2733:     public function add($key, $default, $type, $allow_null)
 2734:     {
 2735:         $obj = new stdclass();
 2736:         $obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type];
 2737:         if ($allow_null) {
 2738:             $obj->allow_null = true;
 2739:         }
 2740:         $this->info[$key] = $obj;
 2741:         $this->defaults[$key] = $default;
 2742:         $this->defaultPlist->set($key, $default);
 2743:     }
 2744: 
 2745:      2746:  2747:  2748:  2749:  2750:  2751:  2752: 
 2753:     public function addValueAliases($key, $aliases)
 2754:     {
 2755:         if (!isset($this->info[$key]->aliases)) {
 2756:             $this->info[$key]->aliases = array();
 2757:         }
 2758:         foreach ($aliases as $alias => $real) {
 2759:             $this->info[$key]->aliases[$alias] = $real;
 2760:         }
 2761:     }
 2762: 
 2763:      2764:  2765:  2766:  2767:  2768:  2769: 
 2770:     public function addAllowedValues($key, $allowed)
 2771:     {
 2772:         $this->info[$key]->allowed = $allowed;
 2773:     }
 2774: 
 2775:      2776:  2777:  2778:  2779: 
 2780:     public function addAlias($key, $new_key)
 2781:     {
 2782:         $obj = new stdclass;
 2783:         $obj->key = $new_key;
 2784:         $obj->isAlias = true;
 2785:         $this->info[$key] = $obj;
 2786:     }
 2787: 
 2788:      2789:  2790: 
 2791:     public function postProcess()
 2792:     {
 2793:         foreach ($this->info as $key => $v) {
 2794:             if (count((array) $v) == 1) {
 2795:                 $this->info[$key] = $v->type;
 2796:             } elseif (count((array) $v) == 2 && isset($v->allow_null)) {
 2797:                 $this->info[$key] = -$v->type;
 2798:             }
 2799:         }
 2800:     }
 2801: }
 2802: 
 2803: 
 2804: 
 2805: 
 2806: 
 2807:  2808:  2809: 
 2810: class HTMLPurifier_ContentSets
 2811: {
 2812: 
 2813:      2814:  2815:  2816: 
 2817:     public $info = array();
 2818: 
 2819:      2820:  2821:  2822:  2823: 
 2824:     public $lookup = array();
 2825: 
 2826:      2827:  2828:  2829: 
 2830:     protected $keys = array();
 2831:      2832:  2833:  2834: 
 2835:     protected $values = array();
 2836: 
 2837:      2838:  2839:  2840:  2841: 
 2842:     public function __construct($modules)
 2843:     {
 2844:         if (!is_array($modules)) {
 2845:             $modules = array($modules);
 2846:         }
 2847:         
 2848:         
 2849:         foreach ($modules as $module) {
 2850:             foreach ($module->content_sets as $key => $value) {
 2851:                 $temp = $this->convertToLookup($value);
 2852:                 if (isset($this->lookup[$key])) {
 2853:                     
 2854:                     $this->lookup[$key] = array_merge($this->lookup[$key], $temp);
 2855:                 } else {
 2856:                     $this->lookup[$key] = $temp;
 2857:                 }
 2858:             }
 2859:         }
 2860:         $old_lookup = false;
 2861:         while ($old_lookup !== $this->lookup) {
 2862:             $old_lookup = $this->lookup;
 2863:             foreach ($this->lookup as $i => $set) {
 2864:                 $add = array();
 2865:                 foreach ($set as $element => $x) {
 2866:                     if (isset($this->lookup[$element])) {
 2867:                         $add += $this->lookup[$element];
 2868:                         unset($this->lookup[$i][$element]);
 2869:                     }
 2870:                 }
 2871:                 $this->lookup[$i] += $add;
 2872:             }
 2873:         }
 2874: 
 2875:         foreach ($this->lookup as $key => $lookup) {
 2876:             $this->info[$key] = implode(' | ', array_keys($lookup));
 2877:         }
 2878:         $this->keys   = array_keys($this->info);
 2879:         $this->values = array_values($this->info);
 2880:     }
 2881: 
 2882:      2883:  2884:  2885:  2886: 
 2887:     public function generateChildDef(&$def, $module)
 2888:     {
 2889:         if (!empty($def->child)) { 
 2890:             return;
 2891:         }
 2892:         $content_model = $def->content_model;
 2893:         if (is_string($content_model)) {
 2894:             
 2895:             $def->content_model = preg_replace_callback(
 2896:                 '/\b(' . implode('|', $this->keys) . ')\b/',
 2897:                 array($this, 'generateChildDefCallback'),
 2898:                 $content_model
 2899:             );
 2900:             
 2901:             
 2902:         }
 2903:         $def->child = $this->getChildDef($def, $module);
 2904:     }
 2905: 
 2906:     public function generateChildDefCallback($matches)
 2907:     {
 2908:         return $this->info[$matches[0]];
 2909:     }
 2910: 
 2911:      2912:  2913:  2914:  2915:  2916:  2917:  2918:  2919: 
 2920:     public function getChildDef($def, $module)
 2921:     {
 2922:         $value = $def->content_model;
 2923:         if (is_object($value)) {
 2924:             trigger_error(
 2925:                 'Literal object child definitions should be stored in '.
 2926:                 'ElementDef->child not ElementDef->content_model',
 2927:                 E_USER_NOTICE
 2928:             );
 2929:             return $value;
 2930:         }
 2931:         switch ($def->content_model_type) {
 2932:             case 'required':
 2933:                 return new HTMLPurifier_ChildDef_Required($value);
 2934:             case 'optional':
 2935:                 return new HTMLPurifier_ChildDef_Optional($value);
 2936:             case 'empty':
 2937:                 return new HTMLPurifier_ChildDef_Empty();
 2938:             case 'custom':
 2939:                 return new HTMLPurifier_ChildDef_Custom($value);
 2940:         }
 2941:         
 2942:         $return = false;
 2943:         if ($module->defines_child_def) { 
 2944:             $return = $module->getChildDef($def);
 2945:         }
 2946:         if ($return !== false) {
 2947:             return $return;
 2948:         }
 2949:         
 2950:         trigger_error(
 2951:             'Could not determine which ChildDef class to instantiate',
 2952:             E_USER_ERROR
 2953:         );
 2954:         return false;
 2955:     }
 2956: 
 2957:      2958:  2959:  2960:  2961:  2962: 
 2963:     protected function convertToLookup($string)
 2964:     {
 2965:         $array = explode('|', str_replace(' ', '', $string));
 2966:         $ret = array();
 2967:         foreach ($array as $k) {
 2968:             $ret[$k] = true;
 2969:         }
 2970:         return $ret;
 2971:     }
 2972: }
 2973: 
 2974: 
 2975: 
 2976: 
 2977: 
 2978:  2979:  2980:  2981:  2982:  2983:  2984: 
 2985: class HTMLPurifier_Context
 2986: {
 2987: 
 2988:      2989:  2990:  2991: 
 2992:     private $_storage = array();
 2993: 
 2994:      2995:  2996:  2997:  2998: 
 2999:     public function register($name, &$ref)
 3000:     {
 3001:         if (array_key_exists($name, $this->_storage)) {
 3002:             trigger_error(
 3003:                 "Name $name produces collision, cannot re-register",
 3004:                 E_USER_ERROR
 3005:             );
 3006:             return;
 3007:         }
 3008:         $this->_storage[$name] =& $ref;
 3009:     }
 3010: 
 3011:      3012:  3013:  3014:  3015:  3016: 
 3017:     public function &get($name, $ignore_error = false)
 3018:     {
 3019:         if (!array_key_exists($name, $this->_storage)) {
 3020:             if (!$ignore_error) {
 3021:                 trigger_error(
 3022:                     "Attempted to retrieve non-existent variable $name",
 3023:                     E_USER_ERROR
 3024:                 );
 3025:             }
 3026:             $var = null; 
 3027:             return $var;
 3028:         }
 3029:         return $this->_storage[$name];
 3030:     }
 3031: 
 3032:      3033:  3034:  3035: 
 3036:     public function destroy($name)
 3037:     {
 3038:         if (!array_key_exists($name, $this->_storage)) {
 3039:             trigger_error(
 3040:                 "Attempted to destroy non-existent variable $name",
 3041:                 E_USER_ERROR
 3042:             );
 3043:             return;
 3044:         }
 3045:         unset($this->_storage[$name]);
 3046:     }
 3047: 
 3048:      3049:  3050:  3051:  3052: 
 3053:     public function exists($name)
 3054:     {
 3055:         return array_key_exists($name, $this->_storage);
 3056:     }
 3057: 
 3058:      3059:  3060:  3061: 
 3062:     public function loadArray($context_array)
 3063:     {
 3064:         foreach ($context_array as $key => $discard) {
 3065:             $this->register($key, $context_array[$key]);
 3066:         }
 3067:     }
 3068: }
 3069: 
 3070: 
 3071: 
 3072: 
 3073: 
 3074:  3075:  3076:  3077:  3078:  3079:  3080:  3081: 
 3082: abstract class HTMLPurifier_DefinitionCache
 3083: {
 3084:      3085:  3086: 
 3087:     public $type;
 3088: 
 3089:      3090:  3091:  3092: 
 3093:     public function __construct($type)
 3094:     {
 3095:         $this->type = $type;
 3096:     }
 3097: 
 3098:      3099:  3100:  3101:  3102: 
 3103:     public function generateKey($config)
 3104:     {
 3105:         return $config->version . ',' . 
 3106:                $config->getBatchSerial($this->type) . ',' .
 3107:                $config->get($this->type . '.DefinitionRev');
 3108:     }
 3109: 
 3110:      3111:  3112:  3113:  3114:  3115:  3116: 
 3117:     public function isOld($key, $config)
 3118:     {
 3119:         if (substr_count($key, ',') < 2) {
 3120:             return true;
 3121:         }
 3122:         list($version, $hash, $revision) = explode(',', $key, 3);
 3123:         $compare = version_compare($version, $config->version);
 3124:         
 3125:         if ($compare != 0) {
 3126:             return true;
 3127:         }
 3128:         
 3129:         if ($hash == $config->getBatchSerial($this->type) &&
 3130:             $revision < $config->get($this->type . '.DefinitionRev')) {
 3131:             return true;
 3132:         }
 3133:         return false;
 3134:     }
 3135: 
 3136:      3137:  3138:  3139:  3140:  3141: 
 3142:     public function checkDefType($def)
 3143:     {
 3144:         if ($def->type !== $this->type) {
 3145:             trigger_error("Cannot use definition of type {$def->type} in cache for {$this->type}");
 3146:             return false;
 3147:         }
 3148:         return true;
 3149:     }
 3150: 
 3151:      3152:  3153:  3154:  3155: 
 3156:     abstract public function add($def, $config);
 3157: 
 3158:      3159:  3160:  3161:  3162: 
 3163:     abstract public function set($def, $config);
 3164: 
 3165:      3166:  3167:  3168:  3169: 
 3170:     abstract public function replace($def, $config);
 3171: 
 3172:      3173:  3174:  3175: 
 3176:     abstract public function get($config);
 3177: 
 3178:      3179:  3180:  3181: 
 3182:     abstract public function remove($config);
 3183: 
 3184:      3185:  3186:  3187: 
 3188:     abstract public function flush($config);
 3189: 
 3190:      3191:  3192:  3193:  3194:  3195:  3196: 
 3197:     abstract public function cleanup($config);
 3198: }
 3199: 
 3200: 
 3201: 
 3202: 
 3203: 
 3204:  3205:  3206: 
 3207: class HTMLPurifier_DefinitionCacheFactory
 3208: {
 3209:      3210:  3211: 
 3212:     protected $caches = array('Serializer' => array());
 3213: 
 3214:      3215:  3216: 
 3217:     protected $implementations = array();
 3218: 
 3219:      3220:  3221: 
 3222:     protected $decorators = array();
 3223: 
 3224:      3225:  3226: 
 3227:     public function setup()
 3228:     {
 3229:         $this->addDecorator('Cleanup');
 3230:     }
 3231: 
 3232:      3233:  3234:  3235:  3236: 
 3237:     public static function instance($prototype = null)
 3238:     {
 3239:         static $instance;
 3240:         if ($prototype !== null) {
 3241:             $instance = $prototype;
 3242:         } elseif ($instance === null || $prototype === true) {
 3243:             $instance = new HTMLPurifier_DefinitionCacheFactory();
 3244:             $instance->setup();
 3245:         }
 3246:         return $instance;
 3247:     }
 3248: 
 3249:      3250:  3251:  3252:  3253: 
 3254:     public function register($short, $long)
 3255:     {
 3256:         $this->implementations[$short] = $long;
 3257:     }
 3258: 
 3259:      3260:  3261:  3262:  3263:  3264: 
 3265:     public function create($type, $config)
 3266:     {
 3267:         $method = $config->get('Cache.DefinitionImpl');
 3268:         if ($method === null) {
 3269:             return new HTMLPurifier_DefinitionCache_Null($type);
 3270:         }
 3271:         if (!empty($this->caches[$method][$type])) {
 3272:             return $this->caches[$method][$type];
 3273:         }
 3274:         if (isset($this->implementations[$method]) &&
 3275:             class_exists($class = $this->implementations[$method], false)) {
 3276:             $cache = new $class($type);
 3277:         } else {
 3278:             if ($method != 'Serializer') {
 3279:                 trigger_error("Unrecognized DefinitionCache $method, using Serializer instead", E_USER_WARNING);
 3280:             }
 3281:             $cache = new HTMLPurifier_DefinitionCache_Serializer($type);
 3282:         }
 3283:         foreach ($this->decorators as $decorator) {
 3284:             $new_cache = $decorator->decorate($cache);
 3285:             
 3286:             unset($cache);
 3287:             $cache = $new_cache;
 3288:         }
 3289:         $this->caches[$method][$type] = $cache;
 3290:         return $this->caches[$method][$type];
 3291:     }
 3292: 
 3293:      3294:  3295:  3296: 
 3297:     public function addDecorator($decorator)
 3298:     {
 3299:         if (is_string($decorator)) {
 3300:             $class = "HTMLPurifier_DefinitionCache_Decorator_$decorator";
 3301:             $decorator = new $class;
 3302:         }
 3303:         $this->decorators[$decorator->name] = $decorator;
 3304:     }
 3305: }
 3306: 
 3307: 
 3308: 
 3309: 
 3310: 
 3311:  3312:  3313:  3314:  3315:  3316: 
 3317: class HTMLPurifier_Doctype
 3318: {
 3319:      3320:  3321:  3322: 
 3323:     public $name;
 3324: 
 3325:      3326:  3327:  3328:  3329: 
 3330:     public $modules = array();
 3331: 
 3332:      3333:  3334:  3335: 
 3336:     public $tidyModules = array();
 3337: 
 3338:      3339:  3340:  3341: 
 3342:     public $xml = true;
 3343: 
 3344:      3345:  3346:  3347: 
 3348:     public $aliases = array();
 3349: 
 3350:      3351:  3352:  3353: 
 3354:     public $dtdPublic;
 3355: 
 3356:      3357:  3358:  3359: 
 3360:     public $dtdSystem;
 3361: 
 3362:     public function __construct(
 3363:         $name = null,
 3364:         $xml = true,
 3365:         $modules = array(),
 3366:         $tidyModules = array(),
 3367:         $aliases = array(),
 3368:         $dtd_public = null,
 3369:         $dtd_system = null
 3370:     ) {
 3371:         $this->name         = $name;
 3372:         $this->xml          = $xml;
 3373:         $this->modules      = $modules;
 3374:         $this->tidyModules  = $tidyModules;
 3375:         $this->aliases      = $aliases;
 3376:         $this->dtdPublic    = $dtd_public;
 3377:         $this->dtdSystem    = $dtd_system;
 3378:     }
 3379: }
 3380: 
 3381: 
 3382: 
 3383: 
 3384: 
 3385: class HTMLPurifier_DoctypeRegistry
 3386: {
 3387: 
 3388:      3389:  3390:  3391: 
 3392:     protected $doctypes;
 3393: 
 3394:      3395:  3396:  3397: 
 3398:     protected $aliases;
 3399: 
 3400:      3401:  3402:  3403:  3404:  3405:  3406:  3407:  3408:  3409:  3410:  3411:  3412: 
 3413:     public function register(
 3414:         $doctype,
 3415:         $xml = true,
 3416:         $modules = array(),
 3417:         $tidy_modules = array(),
 3418:         $aliases = array(),
 3419:         $dtd_public = null,
 3420:         $dtd_system = null
 3421:     ) {
 3422:         if (!is_array($modules)) {
 3423:             $modules = array($modules);
 3424:         }
 3425:         if (!is_array($tidy_modules)) {
 3426:             $tidy_modules = array($tidy_modules);
 3427:         }
 3428:         if (!is_array($aliases)) {
 3429:             $aliases = array($aliases);
 3430:         }
 3431:         if (!is_object($doctype)) {
 3432:             $doctype = new HTMLPurifier_Doctype(
 3433:                 $doctype,
 3434:                 $xml,
 3435:                 $modules,
 3436:                 $tidy_modules,
 3437:                 $aliases,
 3438:                 $dtd_public,
 3439:                 $dtd_system
 3440:             );
 3441:         }
 3442:         $this->doctypes[$doctype->name] = $doctype;
 3443:         $name = $doctype->name;
 3444:         
 3445:         foreach ($doctype->aliases as $alias) {
 3446:             if (isset($this->doctypes[$alias])) {
 3447:                 continue;
 3448:             }
 3449:             $this->aliases[$alias] = $name;
 3450:         }
 3451:         
 3452:         if (isset($this->aliases[$name])) {
 3453:             unset($this->aliases[$name]);
 3454:         }
 3455:         return $doctype;
 3456:     }
 3457: 
 3458:      3459:  3460:  3461:  3462:  3463:  3464: 
 3465:     public function get($doctype)
 3466:     {
 3467:         if (isset($this->aliases[$doctype])) {
 3468:             $doctype = $this->aliases[$doctype];
 3469:         }
 3470:         if (!isset($this->doctypes[$doctype])) {
 3471:             trigger_error('Doctype ' . htmlspecialchars($doctype) . ' does not exist', E_USER_ERROR);
 3472:             $anon = new HTMLPurifier_Doctype($doctype);
 3473:             return $anon;
 3474:         }
 3475:         return $this->doctypes[$doctype];
 3476:     }
 3477: 
 3478:      3479:  3480:  3481:  3482:  3483:  3484:  3485:  3486:  3487: 
 3488:     public function make($config)
 3489:     {
 3490:         return clone $this->get($this->getDoctypeFromConfig($config));
 3491:     }
 3492: 
 3493:      3494:  3495:  3496:  3497: 
 3498:     public function getDoctypeFromConfig($config)
 3499:     {
 3500:         
 3501:         $doctype = $config->get('HTML.Doctype');
 3502:         if (!empty($doctype)) {
 3503:             return $doctype;
 3504:         }
 3505:         $doctype = $config->get('HTML.CustomDoctype');
 3506:         if (!empty($doctype)) {
 3507:             return $doctype;
 3508:         }
 3509:         
 3510:         if ($config->get('HTML.XHTML')) {
 3511:             $doctype = 'XHTML 1.0';
 3512:         } else {
 3513:             $doctype = 'HTML 4.01';
 3514:         }
 3515:         if ($config->get('HTML.Strict')) {
 3516:             $doctype .= ' Strict';
 3517:         } else {
 3518:             $doctype .= ' Transitional';
 3519:         }
 3520:         return $doctype;
 3521:     }
 3522: }
 3523: 
 3524: 
 3525: 
 3526: 
 3527: 
 3528:  3529:  3530:  3531:  3532:  3533:  3534:  3535: 
 3536: class HTMLPurifier_ElementDef
 3537: {
 3538:      3539:  3540:  3541:  3542: 
 3543:     public $standalone = true;
 3544: 
 3545:      3546:  3547:  3548:  3549:  3550:  3551:  3552:  3553:  3554:  3555:  3556: 
 3557:     public $attr = array();
 3558: 
 3559:     
 3560:     
 3561:     
 3562:     
 3563:     
 3564:     
 3565:     
 3566:     
 3567:     
 3568:     
 3569:     
 3570: 
 3571:      3572:  3573:  3574: 
 3575:     public $attr_transform_pre = array();
 3576: 
 3577:      3578:  3579:  3580: 
 3581:     public $attr_transform_post = array();
 3582: 
 3583:      3584:  3585:  3586: 
 3587:     public $child;
 3588: 
 3589:      3590:  3591:  3592:  3593:  3594:  3595:  3596: 
 3597:     public $content_model;
 3598: 
 3599:      3600:  3601:  3602:  3603:  3604:  3605:  3606: 
 3607:     public $content_model_type;
 3608: 
 3609:      3610:  3611:  3612:  3613:  3614:  3615: 
 3616:     public $descendants_are_inline = false;
 3617: 
 3618:      3619:  3620:  3621:  3622: 
 3623:     public $required_attr = array();
 3624: 
 3625:      3626:  3627:  3628:  3629:  3630:  3631:  3632:  3633:  3634:  3635:  3636: 
 3637:     public $excludes = array();
 3638: 
 3639:      3640:  3641:  3642: 
 3643:     public $autoclose = array();
 3644: 
 3645:      3646:  3647:  3648:  3649:  3650: 
 3651:     public $wrap;
 3652: 
 3653:      3654:  3655:  3656:  3657: 
 3658:     public $formatting;
 3659: 
 3660:      3661:  3662: 
 3663:     public static function create($content_model, $content_model_type, $attr)
 3664:     {
 3665:         $def = new HTMLPurifier_ElementDef();
 3666:         $def->content_model = $content_model;
 3667:         $def->content_model_type = $content_model_type;
 3668:         $def->attr = $attr;
 3669:         return $def;
 3670:     }
 3671: 
 3672:      3673:  3674:  3675:  3676:  3677: 
 3678:     public function mergeIn($def)
 3679:     {
 3680:         
 3681:         foreach ($def->attr as $k => $v) {
 3682:             if ($k === 0) {
 3683:                 
 3684:                 
 3685:                 foreach ($v as $v2) {
 3686:                     $this->attr[0][] = $v2;
 3687:                 }
 3688:                 continue;
 3689:             }
 3690:             if ($v === false) {
 3691:                 if (isset($this->attr[$k])) {
 3692:                     unset($this->attr[$k]);
 3693:                 }
 3694:                 continue;
 3695:             }
 3696:             $this->attr[$k] = $v;
 3697:         }
 3698:         $this->_mergeAssocArray($this->excludes, $def->excludes);
 3699:         $this->attr_transform_pre = array_merge($this->attr_transform_pre, $def->attr_transform_pre);
 3700:         $this->attr_transform_post = array_merge($this->attr_transform_post, $def->attr_transform_post);
 3701: 
 3702:         if (!empty($def->content_model)) {
 3703:             $this->content_model =
 3704:                 str_replace("#SUPER", $this->content_model, $def->content_model);
 3705:             $this->child = false;
 3706:         }
 3707:         if (!empty($def->content_model_type)) {
 3708:             $this->content_model_type = $def->content_model_type;
 3709:             $this->child = false;
 3710:         }
 3711:         if (!is_null($def->child)) {
 3712:             $this->child = $def->child;
 3713:         }
 3714:         if (!is_null($def->formatting)) {
 3715:             $this->formatting = $def->formatting;
 3716:         }
 3717:         if ($def->descendants_are_inline) {
 3718:             $this->descendants_are_inline = $def->descendants_are_inline;
 3719:         }
 3720:     }
 3721: 
 3722:      3723:  3724:  3725:  3726: 
 3727:     private function _mergeAssocArray(&$a1, $a2)
 3728:     {
 3729:         foreach ($a2 as $k => $v) {
 3730:             if ($v === false) {
 3731:                 if (isset($a1[$k])) {
 3732:                     unset($a1[$k]);
 3733:                 }
 3734:                 continue;
 3735:             }
 3736:             $a1[$k] = $v;
 3737:         }
 3738:     }
 3739: }
 3740: 
 3741: 
 3742: 
 3743: 
 3744: 
 3745:  3746:  3747:  3748: 
 3749: class HTMLPurifier_Encoder
 3750: {
 3751: 
 3752:      3753:  3754: 
 3755:     private function __construct()
 3756:     {
 3757:         trigger_error('Cannot instantiate encoder, call methods statically', E_USER_ERROR);
 3758:     }
 3759: 
 3760:      3761:  3762: 
 3763:     public static function muteErrorHandler()
 3764:     {
 3765:     }
 3766: 
 3767:      3768:  3769:  3770:  3771:  3772:  3773: 
 3774:     public static function unsafeIconv($in, $out, $text)
 3775:     {
 3776:         set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
 3777:         $r = iconv($in, $out, $text);
 3778:         restore_error_handler();
 3779:         return $r;
 3780:     }
 3781: 
 3782:      3783:  3784:  3785:  3786:  3787:  3788:  3789: 
 3790:     public static function iconv($in, $out, $text, $max_chunk_size = 8000)
 3791:     {
 3792:         $code = self::testIconvTruncateBug();
 3793:         if ($code == self::ICONV_OK) {
 3794:             return self::unsafeIconv($in, $out, $text);
 3795:         } elseif ($code == self::ICONV_TRUNCATES) {
 3796:             
 3797:             
 3798:             if ($in == 'utf-8') {
 3799:                 if ($max_chunk_size < 4) {
 3800:                     trigger_error('max_chunk_size is too small', E_USER_WARNING);
 3801:                     return false;
 3802:                 }
 3803:                 
 3804:                 
 3805:                 if (($c = strlen($text)) <= $max_chunk_size) {
 3806:                     return self::unsafeIconv($in, $out, $text);
 3807:                 }
 3808:                 $r = '';
 3809:                 $i = 0;
 3810:                 while (true) {
 3811:                     if ($i + $max_chunk_size >= $c) {
 3812:                         $r .= self::unsafeIconv($in, $out, substr($text, $i));
 3813:                         break;
 3814:                     }
 3815:                     
 3816:                     if (0x80 != (0xC0 & ord($text[$i + $max_chunk_size]))) {
 3817:                         $chunk_size = $max_chunk_size;
 3818:                     } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 1]))) {
 3819:                         $chunk_size = $max_chunk_size - 1;
 3820:                     } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 2]))) {
 3821:                         $chunk_size = $max_chunk_size - 2;
 3822:                     } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 3]))) {
 3823:                         $chunk_size = $max_chunk_size - 3;
 3824:                     } else {
 3825:                         return false; 
 3826:                     }
 3827:                     $chunk = substr($text, $i, $chunk_size); 
 3828:                     $r .= self::unsafeIconv($in, $out, $chunk);
 3829:                     $i += $chunk_size;
 3830:                 }
 3831:                 return $r;
 3832:             } else {
 3833:                 return false;
 3834:             }
 3835:         } else {
 3836:             return false;
 3837:         }
 3838:     }
 3839: 
 3840:      3841:  3842:  3843:  3844:  3845:  3846:  3847:  3848:  3849:  3850:  3851:  3852:  3853:  3854:  3855:  3856:  3857:  3858:  3859:  3860:  3861:  3862:  3863:  3864:  3865:  3866:  3867:  3868: 
 3869:     public static function cleanUTF8($str, $force_php = false)
 3870:     {
 3871:         
 3872:         
 3873:         
 3874:         
 3875:         
 3876:         if (preg_match(
 3877:             '/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du',
 3878:             $str
 3879:         )) {
 3880:             return $str;
 3881:         }
 3882: 
 3883:         $mState = 0; 
 3884:                      
 3885:         $mUcs4  = 0; 
 3886:         $mBytes = 1; 
 3887: 
 3888:         
 3889:         
 3890:         
 3891:         
 3892:         
 3893: 
 3894:         $out = '';
 3895:         $char = '';
 3896: 
 3897:         $len = strlen($str);
 3898:         for ($i = 0; $i < $len; $i++) {
 3899:             $in = ord($str{$i});
 3900:             $char .= $str[$i]; 
 3901:             if (0 == $mState) {
 3902:                 
 3903:                 
 3904:                 if (0 == (0x80 & ($in))) {
 3905:                     
 3906:                     if (($in <= 31 || $in == 127) &&
 3907:                         !($in == 9 || $in == 13 || $in == 10) 
 3908:                     ) {
 3909:                         
 3910:                     } else {
 3911:                         $out .= $char;
 3912:                     }
 3913:                     
 3914:                     $char = '';
 3915:                     $mBytes = 1;
 3916:                 } elseif (0xC0 == (0xE0 & ($in))) {
 3917:                     
 3918:                     $mUcs4 = ($in);
 3919:                     $mUcs4 = ($mUcs4 & 0x1F) << 6;
 3920:                     $mState = 1;
 3921:                     $mBytes = 2;
 3922:                 } elseif (0xE0 == (0xF0 & ($in))) {
 3923:                     
 3924:                     $mUcs4 = ($in);
 3925:                     $mUcs4 = ($mUcs4 & 0x0F) << 12;
 3926:                     $mState = 2;
 3927:                     $mBytes = 3;
 3928:                 } elseif (0xF0 == (0xF8 & ($in))) {
 3929:                     
 3930:                     $mUcs4 = ($in);
 3931:                     $mUcs4 = ($mUcs4 & 0x07) << 18;
 3932:                     $mState = 3;
 3933:                     $mBytes = 4;
 3934:                 } elseif (0xF8 == (0xFC & ($in))) {
 3935:                     
 3936:                     
 3937:                     
 3938:                     
 3939:                     
 3940:                     
 3941:                     
 3942:                     
 3943:                     
 3944:                     $mUcs4 = ($in);
 3945:                     $mUcs4 = ($mUcs4 & 0x03) << 24;
 3946:                     $mState = 4;
 3947:                     $mBytes = 5;
 3948:                 } elseif (0xFC == (0xFE & ($in))) {
 3949:                     
 3950:                     
 3951:                     $mUcs4 = ($in);
 3952:                     $mUcs4 = ($mUcs4 & 1) << 30;
 3953:                     $mState = 5;
 3954:                     $mBytes = 6;
 3955:                 } else {
 3956:                     
 3957:                     
 3958:                     $mState = 0;
 3959:                     $mUcs4  = 0;
 3960:                     $mBytes = 1;
 3961:                     $char = '';
 3962:                 }
 3963:             } else {
 3964:                 
 3965:                 
 3966:                 if (0x80 == (0xC0 & ($in))) {
 3967:                     
 3968:                     $shift = ($mState - 1) * 6;
 3969:                     $tmp = $in;
 3970:                     $tmp = ($tmp & 0x0000003F) << $shift;
 3971:                     $mUcs4 |= $tmp;
 3972: 
 3973:                     if (0 == --$mState) {
 3974:                         
 3975:                         
 3976: 
 3977:                         
 3978: 
 3979:                         
 3980:                         if (((2 == $mBytes) && ($mUcs4 < 0x0080)) ||
 3981:                             ((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
 3982:                             ((4 == $mBytes) && ($mUcs4 < 0x10000)) ||
 3983:                             (4 < $mBytes) ||
 3984:                             
 3985:                             (($mUcs4 & 0xFFFFF800) == 0xD800) ||
 3986:                             
 3987:                             ($mUcs4 > 0x10FFFF)
 3988:                         ) {
 3989: 
 3990:                         } elseif (0xFEFF != $mUcs4 && 
 3991:                             
 3992:                             (
 3993:                                 0x9 == $mUcs4 ||
 3994:                                 0xA == $mUcs4 ||
 3995:                                 0xD == $mUcs4 ||
 3996:                                 (0x20 <= $mUcs4 && 0x7E >= $mUcs4) ||
 3997:                                 
 3998:                                 
 3999:                                 (0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) ||
 4000:                                 (0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4)
 4001:                             )
 4002:                         ) {
 4003:                             $out .= $char;
 4004:                         }
 4005:                         
 4006:                         $mState = 0;
 4007:                         $mUcs4  = 0;
 4008:                         $mBytes = 1;
 4009:                         $char = '';
 4010:                     }
 4011:                 } else {
 4012:                     
 4013:                     
 4014:                     
 4015:                     $mState = 0;
 4016:                     $mUcs4  = 0;
 4017:                     $mBytes = 1;
 4018:                     $char ='';
 4019:                 }
 4020:             }
 4021:         }
 4022:         return $out;
 4023:     }
 4024: 
 4025:      4026:  4027:  4028:  4029:  4030:  4031:  4032:  4033:  4034:  4035:  4036: 
 4037: 
 4038:     
 4039:     
 4040:     
 4041:     
 4042:     
 4043:     
 4044:     
 4045:     
 4046:     
 4047:     
 4048:     
 4049:     
 4050: 
 4051:     public static function unichr($code)
 4052:     {
 4053:         if ($code > 1114111 or $code < 0 or
 4054:           ($code >= 55296 and $code <= 57343) ) {
 4055:             
 4056:             
 4057:             return '';
 4058:         }
 4059: 
 4060:         $x = $y = $z = $w = 0;
 4061:         if ($code < 128) {
 4062:             
 4063:             $x = $code;
 4064:         } else {
 4065:             
 4066:             $x = ($code & 63) | 128;
 4067:             if ($code < 2048) {
 4068:                 $y = (($code & 2047) >> 6) | 192;
 4069:             } else {
 4070:                 $y = (($code & 4032) >> 6) | 128;
 4071:                 if ($code < 65536) {
 4072:                     $z = (($code >> 12) & 15) | 224;
 4073:                 } else {
 4074:                     $z = (($code >> 12) & 63) | 128;
 4075:                     $w = (($code >> 18) & 7)  | 240;
 4076:                 }
 4077:             }
 4078:         }
 4079:         
 4080:         $ret = '';
 4081:         if ($w) {
 4082:             $ret .= chr($w);
 4083:         }
 4084:         if ($z) {
 4085:             $ret .= chr($z);
 4086:         }
 4087:         if ($y) {
 4088:             $ret .= chr($y);
 4089:         }
 4090:         $ret .= chr($x);
 4091: 
 4092:         return $ret;
 4093:     }
 4094: 
 4095:      4096:  4097: 
 4098:     public static function iconvAvailable()
 4099:     {
 4100:         static $iconv = null;
 4101:         if ($iconv === null) {
 4102:             $iconv = function_exists('iconv') && self::testIconvTruncateBug() != self::ICONV_UNUSABLE;
 4103:         }
 4104:         return $iconv;
 4105:     }
 4106: 
 4107:      4108:  4109:  4110:  4111:  4112:  4113: 
 4114:     public static function convertToUTF8($str, $config, $context)
 4115:     {
 4116:         $encoding = $config->get('Core.Encoding');
 4117:         if ($encoding === 'utf-8') {
 4118:             return $str;
 4119:         }
 4120:         static $iconv = null;
 4121:         if ($iconv === null) {
 4122:             $iconv = self::iconvAvailable();
 4123:         }
 4124:         if ($iconv && !$config->get('Test.ForceNoIconv')) {
 4125:             
 4126:             $str = self::unsafeIconv($encoding, 'utf-8//IGNORE', $str);
 4127:             if ($str === false) {
 4128:                 
 4129:                 trigger_error('Invalid encoding ' . $encoding, E_USER_ERROR);
 4130:                 return '';
 4131:             }
 4132:             
 4133:             
 4134:             
 4135:             $str = strtr($str, self::testEncodingSupportsASCII($encoding));
 4136:             return $str;
 4137:         } elseif ($encoding === 'iso-8859-1') {
 4138:             $str = utf8_encode($str);
 4139:             return $str;
 4140:         }
 4141:         $bug = HTMLPurifier_Encoder::testIconvTruncateBug();
 4142:         if ($bug == self::ICONV_OK) {
 4143:             trigger_error('Encoding not supported, please install iconv', E_USER_ERROR);
 4144:         } else {
 4145:             trigger_error(
 4146:                 'You have a buggy version of iconv, see https://bugs.php.net/bug.php?id=48147 ' .
 4147:                 'and http://sourceware.org/bugzilla/show_bug.cgi?id=13541',
 4148:                 E_USER_ERROR
 4149:             );
 4150:         }
 4151:     }
 4152: 
 4153:      4154:  4155:  4156:  4157:  4158:  4159:  4160:  4161: 
 4162:     public static function convertFromUTF8($str, $config, $context)
 4163:     {
 4164:         $encoding = $config->get('Core.Encoding');
 4165:         if ($escape = $config->get('Core.EscapeNonASCIICharacters')) {
 4166:             $str = self::convertToASCIIDumbLossless($str);
 4167:         }
 4168:         if ($encoding === 'utf-8') {
 4169:             return $str;
 4170:         }
 4171:         static $iconv = null;
 4172:         if ($iconv === null) {
 4173:             $iconv = self::iconvAvailable();
 4174:         }
 4175:         if ($iconv && !$config->get('Test.ForceNoIconv')) {
 4176:             
 4177:             $ascii_fix = self::testEncodingSupportsASCII($encoding);
 4178:             if (!$escape && !empty($ascii_fix)) {
 4179:                 $clear_fix = array();
 4180:                 foreach ($ascii_fix as $utf8 => $native) {
 4181:                     $clear_fix[$utf8] = '';
 4182:                 }
 4183:                 $str = strtr($str, $clear_fix);
 4184:             }
 4185:             $str = strtr($str, array_flip($ascii_fix));
 4186:             
 4187:             $str = self::iconv('utf-8', $encoding . '//IGNORE', $str);
 4188:             return $str;
 4189:         } elseif ($encoding === 'iso-8859-1') {
 4190:             $str = utf8_decode($str);
 4191:             return $str;
 4192:         }
 4193:         trigger_error('Encoding not supported', E_USER_ERROR);
 4194:         
 4195:         
 4196:         
 4197:         
 4198:     }
 4199: 
 4200:      4201:  4202:  4203:  4204:  4205:  4206:  4207:  4208:  4209:  4210:  4211:  4212:  4213:  4214:  4215: 
 4216:     public static function convertToASCIIDumbLossless($str)
 4217:     {
 4218:         $bytesleft = 0;
 4219:         $result = '';
 4220:         $working = 0;
 4221:         $len = strlen($str);
 4222:         for ($i = 0; $i < $len; $i++) {
 4223:             $bytevalue = ord($str[$i]);
 4224:             if ($bytevalue <= 0x7F) { 
 4225:                 $result .= chr($bytevalue);
 4226:                 $bytesleft = 0;
 4227:             } elseif ($bytevalue <= 0xBF) { 
 4228:                 $working = $working << 6;
 4229:                 $working += ($bytevalue & 0x3F);
 4230:                 $bytesleft--;
 4231:                 if ($bytesleft <= 0) {
 4232:                     $result .= "&#" . $working . ";";
 4233:                 }
 4234:             } elseif ($bytevalue <= 0xDF) { 
 4235:                 $working = $bytevalue & 0x1F;
 4236:                 $bytesleft = 1;
 4237:             } elseif ($bytevalue <= 0xEF) { 
 4238:                 $working = $bytevalue & 0x0F;
 4239:                 $bytesleft = 2;
 4240:             } else { 
 4241:                 $working = $bytevalue & 0x07;
 4242:                 $bytesleft = 3;
 4243:             }
 4244:         }
 4245:         return $result;
 4246:     }
 4247: 
 4248:     
 4249:     const ICONV_OK = 0;
 4250: 
 4251:      4252: 
 4253:     const ICONV_TRUNCATES = 1;
 4254: 
 4255:      4256: 
 4257:     const ICONV_UNUSABLE = 2;
 4258: 
 4259:      4260:  4261:  4262:  4263:  4264:  4265:  4266:  4267:  4268:  4269:  4270:  4271:  4272: 
 4273:     public static function testIconvTruncateBug()
 4274:     {
 4275:         static $code = null;
 4276:         if ($code === null) {
 4277:             
 4278:             $r = self::unsafeIconv('utf-8', 'ascii//IGNORE', "\xCE\xB1" . str_repeat('a', 9000));
 4279:             if ($r === false) {
 4280:                 $code = self::ICONV_UNUSABLE;
 4281:             } elseif (($c = strlen($r)) < 9000) {
 4282:                 $code = self::ICONV_TRUNCATES;
 4283:             } elseif ($c > 9000) {
 4284:                 trigger_error(
 4285:                     'Your copy of iconv is extremely buggy. Please notify HTML Purifier maintainers: ' .
 4286:                     'include your iconv version as per phpversion()',
 4287:                     E_USER_ERROR
 4288:                 );
 4289:             } else {
 4290:                 $code = self::ICONV_OK;
 4291:             }
 4292:         }
 4293:         return $code;
 4294:     }
 4295: 
 4296:      4297:  4298:  4299:  4300:  4301:  4302:  4303:  4304:  4305:  4306: 
 4307:     public static function testEncodingSupportsASCII($encoding, $bypass = false)
 4308:     {
 4309:         
 4310:         
 4311:         
 4312:         
 4313:         
 4314:         static $encodings = array();
 4315:         if (!$bypass) {
 4316:             if (isset($encodings[$encoding])) {
 4317:                 return $encodings[$encoding];
 4318:             }
 4319:             $lenc = strtolower($encoding);
 4320:             switch ($lenc) {
 4321:                 case 'shift_jis':
 4322:                     return array("\xC2\xA5" => '\\', "\xE2\x80\xBE" => '~');
 4323:                 case 'johab':
 4324:                     return array("\xE2\x82\xA9" => '\\');
 4325:             }
 4326:             if (strpos($lenc, 'iso-8859-') === 0) {
 4327:                 return array();
 4328:             }
 4329:         }
 4330:         $ret = array();
 4331:         if (self::unsafeIconv('UTF-8', $encoding, 'a') === false) {
 4332:             return false;
 4333:         }
 4334:         for ($i = 0x20; $i <= 0x7E; $i++) { 
 4335:             $c = chr($i); 
 4336:             $r = self::unsafeIconv('UTF-8', "$encoding//IGNORE", $c); 
 4337:             if ($r === '' ||
 4338:                 
 4339:                 
 4340:                 ($r === $c && self::unsafeIconv($encoding, 'UTF-8//IGNORE', $r) !== $c)
 4341:             ) {
 4342:                 
 4343:                 
 4344:                 
 4345:                 $ret[self::unsafeIconv($encoding, 'UTF-8//IGNORE', $c)] = $c;
 4346:             }
 4347:         }
 4348:         $encodings[$encoding] = $ret;
 4349:         return $ret;
 4350:     }
 4351: }
 4352: 
 4353: 
 4354: 
 4355: 
 4356: 
 4357:  4358:  4359: 
 4360: class HTMLPurifier_EntityLookup
 4361: {
 4362:      4363:  4364:  4365: 
 4366:     public $table;
 4367: 
 4368:      4369:  4370:  4371:  4372:  4373:  4374: 
 4375:     public function setup($file = false)
 4376:     {
 4377:         if (!$file) {
 4378:             $file = HTMLPURIFIER_PREFIX . '/HTMLPurifier/EntityLookup/entities.ser';
 4379:         }
 4380:         $this->table = unserialize(file_get_contents($file));
 4381:     }
 4382: 
 4383:      4384:  4385:  4386:  4387: 
 4388:     public static function instance($prototype = false)
 4389:     {
 4390:         
 4391:         static $instance = null;
 4392:         if ($prototype) {
 4393:             $instance = $prototype;
 4394:         } elseif (!$instance) {
 4395:             $instance = new HTMLPurifier_EntityLookup();
 4396:             $instance->setup();
 4397:         }
 4398:         return $instance;
 4399:     }
 4400: }
 4401: 
 4402: 
 4403: 
 4404: 
 4405: 
 4406: 
 4407: 
 4408: 
 4409: 
 4410:  4411:  4412: 
 4413: class HTMLPurifier_EntityParser
 4414: {
 4415: 
 4416:      4417:  4418:  4419: 
 4420:     protected $_entity_lookup;
 4421: 
 4422:      4423:  4424:  4425: 
 4426:     protected $_substituteEntitiesRegex =
 4427:         '/&(?:[#]x([a-fA-F0-9]+)|[#]0*(\d+)|([A-Za-z_:][A-Za-z0-9.\-_:]*));?/';
 4428:         
 4429: 
 4430:      4431:  4432:  4433: 
 4434:     protected $_special_dec2str =
 4435:             array(
 4436:                     34 => '"',
 4437:                     38 => '&',
 4438:                     39 => "'",
 4439:                     60 => '<',
 4440:                     62 => '>'
 4441:             );
 4442: 
 4443:      4444:  4445:  4446: 
 4447:     protected $_special_ent2dec =
 4448:             array(
 4449:                     'quot' => 34,
 4450:                     'amp'  => 38,
 4451:                     'lt'   => 60,
 4452:                     'gt'   => 62
 4453:             );
 4454: 
 4455:      4456:  4457:  4458:  4459:  4460:  4461:  4462: 
 4463:     public function substituteNonSpecialEntities($string)
 4464:     {
 4465:         
 4466:         return preg_replace_callback(
 4467:             $this->_substituteEntitiesRegex,
 4468:             array($this, 'nonSpecialEntityCallback'),
 4469:             $string
 4470:         );
 4471:     }
 4472: 
 4473:      4474:  4475:  4476:  4477:  4478:  4479:  4480: 
 4481: 
 4482:     protected function nonSpecialEntityCallback($matches)
 4483:     {
 4484:         
 4485:         $entity = $matches[0];
 4486:         $is_num = (@$matches[0][1] === '#');
 4487:         if ($is_num) {
 4488:             $is_hex = (@$entity[2] === 'x');
 4489:             $code = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
 4490:             
 4491:             if (isset($this->_special_dec2str[$code])) {
 4492:                 return $entity;
 4493:             }
 4494:             return HTMLPurifier_Encoder::unichr($code);
 4495:         } else {
 4496:             if (isset($this->_special_ent2dec[$matches[3]])) {
 4497:                 return $entity;
 4498:             }
 4499:             if (!$this->_entity_lookup) {
 4500:                 $this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
 4501:             }
 4502:             if (isset($this->_entity_lookup->table[$matches[3]])) {
 4503:                 return $this->_entity_lookup->table[$matches[3]];
 4504:             } else {
 4505:                 return $entity;
 4506:             }
 4507:         }
 4508:     }
 4509: 
 4510:      4511:  4512:  4513:  4514:  4515:  4516:  4517:  4518: 
 4519:     public function substituteSpecialEntities($string)
 4520:     {
 4521:         return preg_replace_callback(
 4522:             $this->_substituteEntitiesRegex,
 4523:             array($this, 'specialEntityCallback'),
 4524:             $string
 4525:         );
 4526:     }
 4527: 
 4528:      4529:  4530:  4531:  4532:  4533:  4534:  4535:  4536:  4537: 
 4538:     protected function specialEntityCallback($matches)
 4539:     {
 4540:         $entity = $matches[0];
 4541:         $is_num = (@$matches[0][1] === '#');
 4542:         if ($is_num) {
 4543:             $is_hex = (@$entity[2] === 'x');
 4544:             $int = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
 4545:             return isset($this->_special_dec2str[$int]) ?
 4546:                 $this->_special_dec2str[$int] :
 4547:                 $entity;
 4548:         } else {
 4549:             return isset($this->_special_ent2dec[$matches[3]]) ?
 4550:                 $this->_special_ent2dec[$matches[3]] :
 4551:                 $entity;
 4552:         }
 4553:     }
 4554: }
 4555: 
 4556: 
 4557: 
 4558: 
 4559: 
 4560:  4561:  4562:  4563: 
 4564: class HTMLPurifier_ErrorCollector
 4565: {
 4566: 
 4567:      4568:  4569:  4570: 
 4571:     const LINENO   = 0;
 4572:     const SEVERITY = 1;
 4573:     const MESSAGE  = 2;
 4574:     const CHILDREN = 3;
 4575: 
 4576:      4577:  4578: 
 4579:     protected $errors;
 4580: 
 4581:      4582:  4583: 
 4584:     protected $_current;
 4585: 
 4586:      4587:  4588: 
 4589:     protected $_stacks = array(array());
 4590: 
 4591:      4592:  4593: 
 4594:     protected $locale;
 4595: 
 4596:      4597:  4598: 
 4599:     protected $generator;
 4600: 
 4601:      4602:  4603: 
 4604:     protected $context;
 4605: 
 4606:      4607:  4608: 
 4609:     protected $lines = array();
 4610: 
 4611:      4612:  4613: 
 4614:     public function __construct($context)
 4615:     {
 4616:         $this->locale    =& $context->get('Locale');
 4617:         $this->context   = $context;
 4618:         $this->_current  =& $this->_stacks[0];
 4619:         $this->errors    =& $this->_stacks[0];
 4620:     }
 4621: 
 4622:      4623:  4624:  4625:  4626: 
 4627:     public function send($severity, $msg)
 4628:     {
 4629:         $args = array();
 4630:         if (func_num_args() > 2) {
 4631:             $args = func_get_args();
 4632:             array_shift($args);
 4633:             unset($args[0]);
 4634:         }
 4635: 
 4636:         $token = $this->context->get('CurrentToken', true);
 4637:         $line  = $token ? $token->line : $this->context->get('CurrentLine', true);
 4638:         $col   = $token ? $token->col  : $this->context->get('CurrentCol', true);
 4639:         $attr  = $this->context->get('CurrentAttr', true);
 4640: 
 4641:         
 4642:         $subst = array();
 4643:         if (!is_null($token)) {
 4644:             $args['CurrentToken'] = $token;
 4645:         }
 4646:         if (!is_null($attr)) {
 4647:             $subst['$CurrentAttr.Name'] = $attr;
 4648:             if (isset($token->attr[$attr])) {
 4649:                 $subst['$CurrentAttr.Value'] = $token->attr[$attr];
 4650:             }
 4651:         }
 4652: 
 4653:         if (empty($args)) {
 4654:             $msg = $this->locale->getMessage($msg);
 4655:         } else {
 4656:             $msg = $this->locale->formatMessage($msg, $args);
 4657:         }
 4658: 
 4659:         if (!empty($subst)) {
 4660:             $msg = strtr($msg, $subst);
 4661:         }
 4662: 
 4663:         
 4664:         $error = array(
 4665:             self::LINENO   => $line,
 4666:             self::SEVERITY => $severity,
 4667:             self::MESSAGE  => $msg,
 4668:             self::CHILDREN => array()
 4669:         );
 4670:         $this->_current[] = $error;
 4671: 
 4672:         
 4673:         
 4674:         
 4675:         
 4676:         $new_struct = new HTMLPurifier_ErrorStruct();
 4677:         $new_struct->type = HTMLPurifier_ErrorStruct::TOKEN;
 4678:         if ($token) {
 4679:             $new_struct->value = clone $token;
 4680:         }
 4681:         if (is_int($line) && is_int($col)) {
 4682:             if (isset($this->lines[$line][$col])) {
 4683:                 $struct = $this->lines[$line][$col];
 4684:             } else {
 4685:                 $struct = $this->lines[$line][$col] = $new_struct;
 4686:             }
 4687:             
 4688:             ksort($this->lines[$line], SORT_NUMERIC);
 4689:         } else {
 4690:             if (isset($this->lines[-1])) {
 4691:                 $struct = $this->lines[-1];
 4692:             } else {
 4693:                 $struct = $this->lines[-1] = $new_struct;
 4694:             }
 4695:         }
 4696:         ksort($this->lines, SORT_NUMERIC);
 4697: 
 4698:         
 4699:         if (!empty($attr)) {
 4700:             $struct = $struct->getChild(HTMLPurifier_ErrorStruct::ATTR, $attr);
 4701:             if (!$struct->value) {
 4702:                 $struct->value = array($attr, 'PUT VALUE HERE');
 4703:             }
 4704:         }
 4705:         if (!empty($cssprop)) {
 4706:             $struct = $struct->getChild(HTMLPurifier_ErrorStruct::CSSPROP, $cssprop);
 4707:             if (!$struct->value) {
 4708:                 
 4709:                 $struct->value = array($cssprop, 'PUT VALUE HERE');
 4710:             }
 4711:         }
 4712: 
 4713:         
 4714:         $struct->addError($severity, $msg);
 4715:     }
 4716: 
 4717:      4718:  4719: 
 4720:     public function getRaw()
 4721:     {
 4722:         return $this->errors;
 4723:     }
 4724: 
 4725:      4726:  4727:  4728:  4729:  4730: 
 4731:     public function getHTMLFormatted($config, $errors = null)
 4732:     {
 4733:         $ret = array();
 4734: 
 4735:         $this->generator = new HTMLPurifier_Generator($config, $this->context);
 4736:         if ($errors === null) {
 4737:             $errors = $this->errors;
 4738:         }
 4739: 
 4740:         
 4741: 
 4742:         
 4743:         foreach ($this->lines as $line => $col_array) {
 4744:             if ($line == -1) {
 4745:                 continue;
 4746:             }
 4747:             foreach ($col_array as $col => $struct) {
 4748:                 $this->_renderStruct($ret, $struct, $line, $col);
 4749:             }
 4750:         }
 4751:         if (isset($this->lines[-1])) {
 4752:             $this->_renderStruct($ret, $this->lines[-1]);
 4753:         }
 4754: 
 4755:         if (empty($errors)) {
 4756:             return '<p>' . $this->locale->getMessage('ErrorCollector: No errors') . '</p>';
 4757:         } else {
 4758:             return '<ul><li>' . implode('</li><li>', $ret) . '</li></ul>';
 4759:         }
 4760: 
 4761:     }
 4762: 
 4763:     private function _renderStruct(&$ret, $struct, $line = null, $col = null)
 4764:     {
 4765:         $stack = array($struct);
 4766:         $context_stack = array(array());
 4767:         while ($current = array_pop($stack)) {
 4768:             $context = array_pop($context_stack);
 4769:             foreach ($current->errors as $error) {
 4770:                 list($severity, $msg) = $error;
 4771:                 $string = '';
 4772:                 $string .= '<div>';
 4773:                 
 4774:                 $error = $this->locale->getErrorName($severity);
 4775:                 $string .= "<span class=\"error e$severity\"><strong>$error</strong></span> ";
 4776:                 if (!is_null($line) && !is_null($col)) {
 4777:                     $string .= "<em class=\"location\">Line $line, Column $col: </em> ";
 4778:                 } else {
 4779:                     $string .= '<em class="location">End of Document: </em> ';
 4780:                 }
 4781:                 $string .= '<strong class="description">' . $this->generator->escape($msg) . '</strong> ';
 4782:                 $string .= '</div>';
 4783:                 
 4784:                 
 4785:                 
 4786:                 
 4787:                 
 4788:                 $ret[] = $string;
 4789:             }
 4790:             foreach ($current->children as $array) {
 4791:                 $context[] = $current;
 4792:                 $stack = array_merge($stack, array_reverse($array, true));
 4793:                 for ($i = count($array); $i > 0; $i--) {
 4794:                     $context_stack[] = $context;
 4795:                 }
 4796:             }
 4797:         }
 4798:     }
 4799: }
 4800: 
 4801: 
 4802: 
 4803: 
 4804: 
 4805:  4806:  4807:  4808:  4809:  4810: 
 4811: class HTMLPurifier_ErrorStruct
 4812: {
 4813: 
 4814:      4815:  4816:  4817: 
 4818:     const TOKEN     = 0;
 4819:     const ATTR      = 1;
 4820:     const CSSPROP   = 2;
 4821: 
 4822:      4823:  4824:  4825: 
 4826:     public $type;
 4827: 
 4828:      4829:  4830:  4831:  4832:  4833:  4834:  4835: 
 4836:     public $value;
 4837: 
 4838:      4839:  4840:  4841: 
 4842:     public $errors = array();
 4843: 
 4844:      4845:  4846:  4847:  4848:  4849: 
 4850:     public $children = array();
 4851: 
 4852:      4853:  4854:  4855:  4856: 
 4857:     public function getChild($type, $id)
 4858:     {
 4859:         if (!isset($this->children[$type][$id])) {
 4860:             $this->children[$type][$id] = new HTMLPurifier_ErrorStruct();
 4861:             $this->children[$type][$id]->type = $type;
 4862:         }
 4863:         return $this->children[$type][$id];
 4864:     }
 4865: 
 4866:      4867:  4868:  4869: 
 4870:     public function addError($severity, $message)
 4871:     {
 4872:         $this->errors[] = array($severity, $message);
 4873:     }
 4874: }
 4875: 
 4876: 
 4877: 
 4878: 
 4879: 
 4880:  4881:  4882:  4883: 
 4884: class HTMLPurifier_Exception extends Exception
 4885: {
 4886: 
 4887: }
 4888: 
 4889: 
 4890: 
 4891: 
 4892: 
 4893:  4894:  4895:  4896:  4897:  4898:  4899:  4900:  4901:  4902:  4903:  4904:  4905:  4906:  4907:  4908:  4909:  4910: 
 4911: 
 4912: class HTMLPurifier_Filter
 4913: {
 4914: 
 4915:      4916:  4917:  4918: 
 4919:     public $name;
 4920: 
 4921:      4922:  4923:  4924:  4925:  4926:  4927: 
 4928:     public function preFilter($html, $config, $context)
 4929:     {
 4930:         return $html;
 4931:     }
 4932: 
 4933:      4934:  4935:  4936:  4937:  4938:  4939: 
 4940:     public function postFilter($html, $config, $context)
 4941:     {
 4942:         return $html;
 4943:     }
 4944: }
 4945: 
 4946: 
 4947: 
 4948: 
 4949: 
 4950:  4951:  4952:  4953:  4954:  4955:  4956: 
 4957: class HTMLPurifier_Generator
 4958: {
 4959: 
 4960:      4961:  4962:  4963: 
 4964:     private $_xhtml = true;
 4965: 
 4966:      4967:  4968:  4969: 
 4970:     private $_scriptFix = false;
 4971: 
 4972:      4973:  4974:  4975:  4976: 
 4977:     private $_def;
 4978: 
 4979:      4980:  4981:  4982: 
 4983:     private $_sortAttr;
 4984: 
 4985:      4986:  4987:  4988: 
 4989:     private $_flashCompat;
 4990: 
 4991:      4992:  4993:  4994: 
 4995:     private $_innerHTMLFix;
 4996: 
 4997:      4998:  4999:  5000:  5001: 
 5002:     private $_flashStack = array();
 5003: 
 5004:      5005:  5006:  5007: 
 5008:     protected $config;
 5009: 
 5010:      5011:  5012:  5013: 
 5014:     public function __construct($config, $context)
 5015:     {
 5016:         $this->config = $config;
 5017:         $this->_scriptFix = $config->get('Output.CommentScriptContents');
 5018:         $this->_innerHTMLFix = $config->get('Output.FixInnerHTML');
 5019:         $this->_sortAttr = $config->get('Output.SortAttr');
 5020:         $this->_flashCompat = $config->get('Output.FlashCompat');
 5021:         $this->_def = $config->getHTMLDefinition();
 5022:         $this->_xhtml = $this->_def->doctype->xml;
 5023:     }
 5024: 
 5025:      5026:  5027:  5028:  5029: 
 5030:     public function generateFromTokens($tokens)
 5031:     {
 5032:         if (!$tokens) {
 5033:             return '';
 5034:         }
 5035: 
 5036:         
 5037:         $html = '';
 5038:         for ($i = 0, $size = count($tokens); $i < $size; $i++) {
 5039:             if ($this->_scriptFix && $tokens[$i]->name === 'script'
 5040:                 && $i + 2 < $size && $tokens[$i+2] instanceof HTMLPurifier_Token_End) {
 5041:                 
 5042:                 
 5043:                 
 5044:                 $html .= $this->generateFromToken($tokens[$i++]);
 5045:                 $html .= $this->generateScriptFromToken($tokens[$i++]);
 5046:             }
 5047:             $html .= $this->generateFromToken($tokens[$i]);
 5048:         }
 5049: 
 5050:         
 5051:         if (extension_loaded('tidy') && $this->config->get('Output.TidyFormat')) {
 5052:             $tidy = new Tidy;
 5053:             $tidy->parseString(
 5054:                 $html,
 5055:                 array(
 5056:                    'indent'=> true,
 5057:                    'output-xhtml' => $this->_xhtml,
 5058:                    'show-body-only' => true,
 5059:                    'indent-spaces' => 2,
 5060:                    'wrap' => 68,
 5061:                 ),
 5062:                 'utf8'
 5063:             );
 5064:             $tidy->cleanRepair();
 5065:             $html = (string) $tidy; 
 5066:         }
 5067: 
 5068:         
 5069:         if ($this->config->get('Core.NormalizeNewlines')) {
 5070:             $nl = $this->config->get('Output.Newline');
 5071:             if ($nl === null) {
 5072:                 $nl = PHP_EOL;
 5073:             }
 5074:             if ($nl !== "\n") {
 5075:                 $html = str_replace("\n", $nl, $html);
 5076:             }
 5077:         }
 5078:         return $html;
 5079:     }
 5080: 
 5081:      5082:  5083:  5084:  5085: 
 5086:     public function generateFromToken($token)
 5087:     {
 5088:         if (!$token instanceof HTMLPurifier_Token) {
 5089:             trigger_error('Cannot generate HTML from non-HTMLPurifier_Token object', E_USER_WARNING);
 5090:             return '';
 5091: 
 5092:         } elseif ($token instanceof HTMLPurifier_Token_Start) {
 5093:             $attr = $this->generateAttributes($token->attr, $token->name);
 5094:             if ($this->_flashCompat) {
 5095:                 if ($token->name == "object") {
 5096:                     $flash = new stdclass();
 5097:                     $flash->attr = $token->attr;
 5098:                     $flash->param = array();
 5099:                     $this->_flashStack[] = $flash;
 5100:                 }
 5101:             }
 5102:             return '<' . $token->name . ($attr ? ' ' : '') . $attr . '>';
 5103: 
 5104:         } elseif ($token instanceof HTMLPurifier_Token_End) {
 5105:             $_extra = '';
 5106:             if ($this->_flashCompat) {
 5107:                 if ($token->name == "object" && !empty($this->_flashStack)) {
 5108:                     
 5109:                 }
 5110:             }
 5111:             return $_extra . '</' . $token->name . '>';
 5112: 
 5113:         } elseif ($token instanceof HTMLPurifier_Token_Empty) {
 5114:             if ($this->_flashCompat && $token->name == "param" && !empty($this->_flashStack)) {
 5115:                 $this->_flashStack[count($this->_flashStack)-1]->param[$token->attr['name']] = $token->attr['value'];
 5116:             }
 5117:             $attr = $this->generateAttributes($token->attr, $token->name);
 5118:              return '<' . $token->name . ($attr ? ' ' : '') . $attr .
 5119:                 ( $this->_xhtml ? ' /': '' ) 
 5120:                 . '>';
 5121: 
 5122:         } elseif ($token instanceof HTMLPurifier_Token_Text) {
 5123:             return $this->escape($token->data, ENT_NOQUOTES);
 5124: 
 5125:         } elseif ($token instanceof HTMLPurifier_Token_Comment) {
 5126:             return '<!--' . $token->data . '-->';
 5127:         } else {
 5128:             return '';
 5129: 
 5130:         }
 5131:     }
 5132: 
 5133:      5134:  5135:  5136:  5137:  5138:  5139: 
 5140:     public function generateScriptFromToken($token)
 5141:     {
 5142:         if (!$token instanceof HTMLPurifier_Token_Text) {
 5143:             return $this->generateFromToken($token);
 5144:         }
 5145:         
 5146:         $data = preg_replace('#//\s*$#', '', $token->data);
 5147:         return '<!--//--><![CDATA[//><!--' . "\n" . trim($data) . "\n" . '//--><!]]>';
 5148:     }
 5149: 
 5150:      5151:  5152:  5153:  5154:  5155:  5156:  5157: 
 5158:     public function generateAttributes($assoc_array_of_attributes, $element = '')
 5159:     {
 5160:         $html = '';
 5161:         if ($this->_sortAttr) {
 5162:             ksort($assoc_array_of_attributes);
 5163:         }
 5164:         foreach ($assoc_array_of_attributes as $key => $value) {
 5165:             if (!$this->_xhtml) {
 5166:                 
 5167:                 if (strpos($key, ':') !== false) {
 5168:                     continue;
 5169:                 }
 5170:                 
 5171:                 if ($element && !empty($this->_def->info[$element]->attr[$key]->minimized)) {
 5172:                     $html .= $key . ' ';
 5173:                     continue;
 5174:                 }
 5175:             }
 5176:             
 5177:             
 5178:             
 5179:             
 5180:             
 5181:             
 5182:             
 5183:             
 5184:             
 5185:             
 5186:             
 5187:             
 5188:             
 5189:             
 5190:             
 5191:             
 5192:             
 5193:             
 5194:             
 5195:             
 5196:             
 5197:             if ($this->_innerHTMLFix) {
 5198:                 if (strpos($value, '`') !== false) {
 5199:                     
 5200:                     
 5201:                     if (strcspn($value, '"\' <>') === strlen($value)) {
 5202:                         
 5203:                         $value .= ' ';
 5204:                     }
 5205:                 }
 5206:             }
 5207:             $html .= $key.'="'.$this->escape($value).'" ';
 5208:         }
 5209:         return rtrim($html);
 5210:     }
 5211: 
 5212:      5213:  5214:  5215:  5216:  5217:  5218:  5219:  5220:  5221: 
 5222:     public function escape($string, $quote = null)
 5223:     {
 5224:         
 5225:         
 5226:         if ($quote === null) {
 5227:             $quote = ENT_COMPAT;
 5228:         }
 5229:         return htmlspecialchars($string, $quote, 'UTF-8');
 5230:     }
 5231: }
 5232: 
 5233: 
 5234: 
 5235: 
 5236: 
 5237:  5238:  5239:  5240:  5241:  5242:  5243:  5244:  5245:  5246:  5247:  5248:  5249:  5250:  5251:  5252:  5253:  5254:  5255:  5256:  5257:  5258:  5259: 
 5260: class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition
 5261: {
 5262: 
 5263:     
 5264: 
 5265:      5266:  5267:  5268: 
 5269:     public $info = array();
 5270: 
 5271:      5272:  5273:  5274: 
 5275:     public $info_global_attr = array();
 5276: 
 5277:      5278:  5279:  5280: 
 5281:     public $info_parent = 'div';
 5282: 
 5283:      5284:  5285:  5286:  5287: 
 5288:     public $info_parent_def;
 5289: 
 5290:      5291:  5292:  5293:  5294: 
 5295:     public $info_block_wrapper = 'p';
 5296: 
 5297:      5298:  5299:  5300: 
 5301:     public $info_tag_transform = array();
 5302: 
 5303:      5304:  5305:  5306: 
 5307:     public $info_attr_transform_pre = array();
 5308: 
 5309:      5310:  5311:  5312: 
 5313:     public $info_attr_transform_post = array();
 5314: 
 5315:      5316:  5317:  5318:  5319: 
 5320:     public $info_content_sets = array();
 5321: 
 5322:      5323:  5324:  5325: 
 5326:     public $info_injector = array();
 5327: 
 5328:      5329:  5330:  5331: 
 5332:     public $doctype;
 5333: 
 5334: 
 5335: 
 5336:     
 5337: 
 5338:      5339:  5340:  5341:  5342:  5343:  5344:  5345:  5346: 
 5347:     public function addAttribute($element_name, $attr_name, $def)
 5348:     {
 5349:         $module = $this->getAnonymousModule();
 5350:         if (!isset($module->info[$element_name])) {
 5351:             $element = $module->addBlankElement($element_name);
 5352:         } else {
 5353:             $element = $module->info[$element_name];
 5354:         }
 5355:         $element->attr[$attr_name] = $def;
 5356:     }
 5357: 
 5358:      5359:  5360:  5361:  5362: 
 5363:     public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array())
 5364:     {
 5365:         $module = $this->getAnonymousModule();
 5366:         
 5367:         
 5368:         $element = $module->addElement($element_name, $type, $contents, $attr_collections, $attributes);
 5369:         return $element;
 5370:     }
 5371: 
 5372:      5373:  5374:  5375:  5376:  5377:  5378:  5379: 
 5380:     public function addBlankElement($element_name)
 5381:     {
 5382:         $module  = $this->getAnonymousModule();
 5383:         $element = $module->addBlankElement($element_name);
 5384:         return $element;
 5385:     }
 5386: 
 5387:      5388:  5389:  5390:  5391:  5392: 
 5393:     public function getAnonymousModule()
 5394:     {
 5395:         if (!$this->_anonModule) {
 5396:             $this->_anonModule = new HTMLPurifier_HTMLModule();
 5397:             $this->_anonModule->name = 'Anonymous';
 5398:         }
 5399:         return $this->_anonModule;
 5400:     }
 5401: 
 5402:     private $_anonModule = null;
 5403: 
 5404:     
 5405: 
 5406:      5407:  5408: 
 5409:     public $type = 'HTML';
 5410: 
 5411:      5412:  5413: 
 5414:     public $manager;
 5415: 
 5416:      5417:  5418: 
 5419:     public function __construct()
 5420:     {
 5421:         $this->manager = new HTMLPurifier_HTMLModuleManager();
 5422:     }
 5423: 
 5424:      5425:  5426: 
 5427:     protected function doSetup($config)
 5428:     {
 5429:         $this->processModules($config);
 5430:         $this->setupConfigStuff($config);
 5431:         unset($this->manager);
 5432: 
 5433:         
 5434:         foreach ($this->info as $k => $v) {
 5435:             unset($this->info[$k]->content_model);
 5436:             unset($this->info[$k]->content_model_type);
 5437:         }
 5438:     }
 5439: 
 5440:      5441:  5442:  5443: 
 5444:     protected function processModules($config)
 5445:     {
 5446:         if ($this->_anonModule) {
 5447:             
 5448:             
 5449:             
 5450:             $this->manager->addModule($this->_anonModule);
 5451:             unset($this->_anonModule);
 5452:         }
 5453: 
 5454:         $this->manager->setup($config);
 5455:         $this->doctype = $this->manager->doctype;
 5456: 
 5457:         foreach ($this->manager->modules as $module) {
 5458:             foreach ($module->info_tag_transform as $k => $v) {
 5459:                 if ($v === false) {
 5460:                     unset($this->info_tag_transform[$k]);
 5461:                 } else {
 5462:                     $this->info_tag_transform[$k] = $v;
 5463:                 }
 5464:             }
 5465:             foreach ($module->info_attr_transform_pre as $k => $v) {
 5466:                 if ($v === false) {
 5467:                     unset($this->info_attr_transform_pre[$k]);
 5468:                 } else {
 5469:                     $this->info_attr_transform_pre[$k] = $v;
 5470:                 }
 5471:             }
 5472:             foreach ($module->info_attr_transform_post as $k => $v) {
 5473:                 if ($v === false) {
 5474:                     unset($this->info_attr_transform_post[$k]);
 5475:                 } else {
 5476:                     $this->info_attr_transform_post[$k] = $v;
 5477:                 }
 5478:             }
 5479:             foreach ($module->info_injector as $k => $v) {
 5480:                 if ($v === false) {
 5481:                     unset($this->info_injector[$k]);
 5482:                 } else {
 5483:                     $this->info_injector[$k] = $v;
 5484:                 }
 5485:             }
 5486:         }
 5487:         $this->info = $this->manager->getElements();
 5488:         $this->info_content_sets = $this->manager->contentSets->lookup;
 5489:     }
 5490: 
 5491:      5492:  5493:  5494: 
 5495:     protected function setupConfigStuff($config)
 5496:     {
 5497:         $block_wrapper = $config->get('HTML.BlockWrapper');
 5498:         if (isset($this->info_content_sets['Block'][$block_wrapper])) {
 5499:             $this->info_block_wrapper = $block_wrapper;
 5500:         } else {
 5501:             trigger_error(
 5502:                 'Cannot use non-block element as block wrapper',
 5503:                 E_USER_ERROR
 5504:             );
 5505:         }
 5506: 
 5507:         $parent = $config->get('HTML.Parent');
 5508:         $def = $this->manager->getElement($parent, true);
 5509:         if ($def) {
 5510:             $this->info_parent = $parent;
 5511:             $this->info_parent_def = $def;
 5512:         } else {
 5513:             trigger_error(
 5514:                 'Cannot use unrecognized element as parent',
 5515:                 E_USER_ERROR
 5516:             );
 5517:             $this->info_parent_def = $this->manager->getElement($this->info_parent, true);
 5518:         }
 5519: 
 5520:         
 5521:         $support = "(for information on implementing this, see the support forums) ";
 5522: 
 5523:         
 5524: 
 5525:         $allowed_elements = $config->get('HTML.AllowedElements');
 5526:         $allowed_attributes = $config->get('HTML.AllowedAttributes'); 
 5527: 
 5528:         if (!is_array($allowed_elements) && !is_array($allowed_attributes)) {
 5529:             $allowed = $config->get('HTML.Allowed');
 5530:             if (is_string($allowed)) {
 5531:                 list($allowed_elements, $allowed_attributes) = $this->parseTinyMCEAllowedList($allowed);
 5532:             }
 5533:         }
 5534: 
 5535:         if (is_array($allowed_elements)) {
 5536:             foreach ($this->info as $name => $d) {
 5537:                 if (!isset($allowed_elements[$name])) {
 5538:                     unset($this->info[$name]);
 5539:                 }
 5540:                 unset($allowed_elements[$name]);
 5541:             }
 5542:             
 5543:             foreach ($allowed_elements as $element => $d) {
 5544:                 $element = htmlspecialchars($element); 
 5545:                 trigger_error("Element '$element' is not supported $support", E_USER_WARNING);
 5546:             }
 5547:         }
 5548: 
 5549:         
 5550: 
 5551:         $allowed_attributes_mutable = $allowed_attributes; 
 5552:         if (is_array($allowed_attributes)) {
 5553:             
 5554:             
 5555:             
 5556:             foreach ($this->info_global_attr as $attr => $x) {
 5557:                 $keys = array($attr, "*@$attr", "*.$attr");
 5558:                 $delete = true;
 5559:                 foreach ($keys as $key) {
 5560:                     if ($delete && isset($allowed_attributes[$key])) {
 5561:                         $delete = false;
 5562:                     }
 5563:                     if (isset($allowed_attributes_mutable[$key])) {
 5564:                         unset($allowed_attributes_mutable[$key]);
 5565:                     }
 5566:                 }
 5567:                 if ($delete) {
 5568:                     unset($this->info_global_attr[$attr]);
 5569:                 }
 5570:             }
 5571: 
 5572:             foreach ($this->info as $tag => $info) {
 5573:                 foreach ($info->attr as $attr => $x) {
 5574:                     $keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr");
 5575:                     $delete = true;
 5576:                     foreach ($keys as $key) {
 5577:                         if ($delete && isset($allowed_attributes[$key])) {
 5578:                             $delete = false;
 5579:                         }
 5580:                         if (isset($allowed_attributes_mutable[$key])) {
 5581:                             unset($allowed_attributes_mutable[$key]);
 5582:                         }
 5583:                     }
 5584:                     if ($delete) {
 5585:                         if ($this->info[$tag]->attr[$attr]->required) {
 5586:                             trigger_error(
 5587:                                 "Required attribute '$attr' in element '$tag' " .
 5588:                                 "was not allowed, which means '$tag' will not be allowed either",
 5589:                                 E_USER_WARNING
 5590:                             );
 5591:                         }
 5592:                         unset($this->info[$tag]->attr[$attr]);
 5593:                     }
 5594:                 }
 5595:             }
 5596:             
 5597:             foreach ($allowed_attributes_mutable as $elattr => $d) {
 5598:                 $bits = preg_split('/[.@]/', $elattr, 2);
 5599:                 $c = count($bits);
 5600:                 switch ($c) {
 5601:                     case 2:
 5602:                         if ($bits[0] !== '*') {
 5603:                             $element = htmlspecialchars($bits[0]);
 5604:                             $attribute = htmlspecialchars($bits[1]);
 5605:                             if (!isset($this->info[$element])) {
 5606:                                 trigger_error(
 5607:                                     "Cannot allow attribute '$attribute' if element " .
 5608:                                     "'$element' is not allowed/supported $support"
 5609:                                 );
 5610:                             } else {
 5611:                                 trigger_error(
 5612:                                     "Attribute '$attribute' in element '$element' not supported $support",
 5613:                                     E_USER_WARNING
 5614:                                 );
 5615:                             }
 5616:                             break;
 5617:                         }
 5618:                         
 5619:                     case 1:
 5620:                         $attribute = htmlspecialchars($bits[0]);
 5621:                         trigger_error(
 5622:                             "Global attribute '$attribute' is not ".
 5623:                             "supported in any elements $support",
 5624:                             E_USER_WARNING
 5625:                         );
 5626:                         break;
 5627:                 }
 5628:             }
 5629:         }
 5630: 
 5631:         
 5632: 
 5633:         $forbidden_elements   = $config->get('HTML.ForbiddenElements');
 5634:         $forbidden_attributes = $config->get('HTML.ForbiddenAttributes');
 5635: 
 5636:         foreach ($this->info as $tag => $info) {
 5637:             if (isset($forbidden_elements[$tag])) {
 5638:                 unset($this->info[$tag]);
 5639:                 continue;
 5640:             }
 5641:             foreach ($info->attr as $attr => $x) {
 5642:                 if (isset($forbidden_attributes["$tag@$attr"]) ||
 5643:                     isset($forbidden_attributes["*@$attr"]) ||
 5644:                     isset($forbidden_attributes[$attr])
 5645:                 ) {
 5646:                     unset($this->info[$tag]->attr[$attr]);
 5647:                     continue;
 5648:                 } elseif (isset($forbidden_attributes["$tag.$attr"])) { 
 5649:                     
 5650:                     trigger_error(
 5651:                         "Error with $tag.$attr: tag.attr syntax not supported for " .
 5652:                         "HTML.ForbiddenAttributes; use tag@attr instead",
 5653:                         E_USER_WARNING
 5654:                     );
 5655:                 }
 5656:             }
 5657:         }
 5658:         foreach ($forbidden_attributes as $key => $v) {
 5659:             if (strlen($key) < 2) {
 5660:                 continue;
 5661:             }
 5662:             if ($key[0] != '*') {
 5663:                 continue;
 5664:             }
 5665:             if ($key[1] == '.') {
 5666:                 trigger_error(
 5667:                     "Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead",
 5668:                     E_USER_WARNING
 5669:                 );
 5670:             }
 5671:         }
 5672: 
 5673:         
 5674:         foreach ($this->info_injector as $i => $injector) {
 5675:             if ($injector->checkNeeded($config) !== false) {
 5676:                 
 5677:                 
 5678:                 unset($this->info_injector[$i]);
 5679:             }
 5680:         }
 5681:     }
 5682: 
 5683:      5684:  5685:  5686:  5687:  5688:  5689:  5690:  5691: 
 5692:     public function parseTinyMCEAllowedList($list)
 5693:     {
 5694:         $list = str_replace(array(' ', "\t"), '', $list);
 5695: 
 5696:         $elements = array();
 5697:         $attributes = array();
 5698: 
 5699:         $chunks = preg_split('/(,|[\n\r]+)/', $list);
 5700:         foreach ($chunks as $chunk) {
 5701:             if (empty($chunk)) {
 5702:                 continue;
 5703:             }
 5704:             
 5705:             if (!strpos($chunk, '[')) {
 5706:                 $element = $chunk;
 5707:                 $attr = false;
 5708:             } else {
 5709:                 list($element, $attr) = explode('[', $chunk);
 5710:             }
 5711:             if ($element !== '*') {
 5712:                 $elements[$element] = true;
 5713:             }
 5714:             if (!$attr) {
 5715:                 continue;
 5716:             }
 5717:             $attr = substr($attr, 0, strlen($attr) - 1); 
 5718:             $attr = explode('|', $attr);
 5719:             foreach ($attr as $key) {
 5720:                 $attributes["$element.$key"] = true;
 5721:             }
 5722:         }
 5723:         return array($elements, $attributes);
 5724:     }
 5725: }
 5726: 
 5727: 
 5728: 
 5729: 
 5730: 
 5731:  5732:  5733:  5734:  5735:  5736:  5737:  5738:  5739:  5740:  5741:  5742:  5743:  5744: 
 5745: 
 5746: class HTMLPurifier_HTMLModule
 5747: {
 5748: 
 5749:     
 5750: 
 5751:      5752:  5753:  5754: 
 5755:     public $name;
 5756: 
 5757:      5758:  5759:  5760:  5761: 
 5762:     public $elements = array();
 5763: 
 5764:      5765:  5766:  5767:  5768:  5769: 
 5770:     public $info = array();
 5771: 
 5772:      5773:  5774:  5775:  5776:  5777:  5778: 
 5779:     public $content_sets = array();
 5780: 
 5781:      5782:  5783:  5784:  5785:  5786:  5787:  5788:  5789: 
 5790:     public $attr_collections = array();
 5791: 
 5792:      5793:  5794:  5795: 
 5796:     public $info_tag_transform = array();
 5797: 
 5798:      5799:  5800:  5801: 
 5802:     public $info_attr_transform_pre = array();
 5803: 
 5804:      5805:  5806:  5807: 
 5808:     public $info_attr_transform_post = array();
 5809: 
 5810:      5811:  5812:  5813:  5814:  5815:  5816: 
 5817:     public $info_injector = array();
 5818: 
 5819:      5820:  5821:  5822:  5823:  5824:  5825: 
 5826:     public $defines_child_def = false;
 5827: 
 5828:      5829:  5830:  5831:  5832:  5833:  5834:  5835:  5836:  5837:  5838:  5839:  5840: 
 5841:     public $safe = true;
 5842: 
 5843:      5844:  5845:  5846:  5847:  5848:  5849:  5850: 
 5851:     public function getChildDef($def)
 5852:     {
 5853:         return false;
 5854:     }
 5855: 
 5856:     
 5857: 
 5858:      5859:  5860:  5861:  5862:  5863:  5864:  5865:  5866:  5867:  5868:  5869:  5870:  5871: 
 5872:     public function addElement($element, $type, $contents, $attr_includes = array(), $attr = array())
 5873:     {
 5874:         $this->elements[] = $element;
 5875:         
 5876:         list($content_model_type, $content_model) = $this->parseContents($contents);
 5877:         
 5878:         $this->mergeInAttrIncludes($attr, $attr_includes);
 5879:         
 5880:         if ($type) {
 5881:             $this->addElementToContentSet($element, $type);
 5882:         }
 5883:         
 5884:         $this->info[$element] = HTMLPurifier_ElementDef::create(
 5885:             $content_model,
 5886:             $content_model_type,
 5887:             $attr
 5888:         );
 5889:         
 5890:         if (!is_string($contents)) {
 5891:             $this->info[$element]->child = $contents;
 5892:         }
 5893:         return $this->info[$element];
 5894:     }
 5895: 
 5896:      5897:  5898:  5899:  5900:  5901: 
 5902:     public function addBlankElement($element)
 5903:     {
 5904:         if (!isset($this->info[$element])) {
 5905:             $this->elements[] = $element;
 5906:             $this->info[$element] = new HTMLPurifier_ElementDef();
 5907:             $this->info[$element]->standalone = false;
 5908:         } else {
 5909:             trigger_error("Definition for $element already exists in module, cannot redefine");
 5910:         }
 5911:         return $this->info[$element];
 5912:     }
 5913: 
 5914:      5915:  5916:  5917:  5918:  5919: 
 5920:     public function addElementToContentSet($element, $type)
 5921:     {
 5922:         if (!isset($this->content_sets[$type])) {
 5923:             $this->content_sets[$type] = '';
 5924:         } else {
 5925:             $this->content_sets[$type] .= ' | ';
 5926:         }
 5927:         $this->content_sets[$type] .= $element;
 5928:     }
 5929: 
 5930:      5931:  5932:  5933:  5934:  5935:  5936:  5937:  5938:  5939: 
 5940:     public function parseContents($contents)
 5941:     {
 5942:         if (!is_string($contents)) {
 5943:             return array(null, null);
 5944:         } 
 5945:         switch ($contents) {
 5946:             
 5947:             case 'Empty':
 5948:                 return array('empty', '');
 5949:             case 'Inline':
 5950:                 return array('optional', 'Inline | #PCDATA');
 5951:             case 'Flow':
 5952:                 return array('optional', 'Flow | #PCDATA');
 5953:         }
 5954:         list($content_model_type, $content_model) = explode(':', $contents);
 5955:         $content_model_type = strtolower(trim($content_model_type));
 5956:         $content_model = trim($content_model);
 5957:         return array($content_model_type, $content_model);
 5958:     }
 5959: 
 5960:      5961:  5962:  5963:  5964:  5965: 
 5966:     public function mergeInAttrIncludes(&$attr, $attr_includes)
 5967:     {
 5968:         if (!is_array($attr_includes)) {
 5969:             if (empty($attr_includes)) {
 5970:                 $attr_includes = array();
 5971:             } else {
 5972:                 $attr_includes = array($attr_includes);
 5973:             }
 5974:         }
 5975:         $attr[0] = $attr_includes;
 5976:     }
 5977: 
 5978:      5979:  5980:  5981:  5982:  5983:  5984:  5985: 
 5986:     public function makeLookup($list)
 5987:     {
 5988:         if (is_string($list)) {
 5989:             $list = func_get_args();
 5990:         }
 5991:         $ret = array();
 5992:         foreach ($list as $value) {
 5993:             if (is_null($value)) {
 5994:                 continue;
 5995:             }
 5996:             $ret[$value] = true;
 5997:         }
 5998:         return $ret;
 5999:     }
 6000: 
 6001:      6002:  6003:  6004:  6005:  6006: 
 6007:     public function setup($config)
 6008:     {
 6009:     }
 6010: }
 6011: 
 6012: 
 6013: 
 6014: 
 6015: 
 6016: class HTMLPurifier_HTMLModuleManager
 6017: {
 6018: 
 6019:      6020:  6021: 
 6022:     public $doctypes;
 6023: 
 6024:      6025:  6026:  6027: 
 6028:     public $doctype;
 6029: 
 6030:      6031:  6032: 
 6033:     public $attrTypes;
 6034: 
 6035:      6036:  6037:  6038:  6039: 
 6040:     public $modules = array();
 6041: 
 6042:      6043:  6044:  6045:  6046:  6047: 
 6048:     public $registeredModules = array();
 6049: 
 6050:      6051:  6052:  6053:  6054:  6055: 
 6056:     public $userModules = array();
 6057: 
 6058:      6059:  6060:  6061:  6062: 
 6063:     public $elementLookup = array();
 6064: 
 6065:      6066:  6067:  6068: 
 6069:     public $prefixes = array('HTMLPurifier_HTMLModule_');
 6070: 
 6071:      6072:  6073: 
 6074:     public $contentSets;
 6075: 
 6076:      6077:  6078: 
 6079:     public $attrCollections;
 6080: 
 6081:      6082:  6083:  6084: 
 6085:     public $trusted = false;
 6086: 
 6087:     public function __construct()
 6088:     {
 6089:         
 6090:         $this->attrTypes = new HTMLPurifier_AttrTypes();
 6091:         $this->doctypes  = new HTMLPurifier_DoctypeRegistry();
 6092: 
 6093:         
 6094:         $common = array(
 6095:             'CommonAttributes', 'Text', 'Hypertext', 'List',
 6096:             'Presentation', 'Edit', 'Bdo', 'Tables', 'Image',
 6097:             'StyleAttribute',
 6098:             
 6099:             'Scripting', 'Object', 'Forms',
 6100:             
 6101:             'Name',
 6102:         );
 6103:         $transitional = array('Legacy', 'Target', 'Iframe');
 6104:         $xml = array('XMLCommonAttributes');
 6105:         $non_xml = array('NonXMLCommonAttributes');
 6106: 
 6107:         
 6108:         $this->doctypes->register(
 6109:             'HTML 4.01 Transitional',
 6110:             false,
 6111:             array_merge($common, $transitional, $non_xml),
 6112:             array('Tidy_Transitional', 'Tidy_Proprietary'),
 6113:             array(),
 6114:             '-//W3C//DTD HTML 4.01 Transitional//EN',
 6115:             'http://www.w3.org/TR/html4/loose.dtd'
 6116:         );
 6117: 
 6118:         $this->doctypes->register(
 6119:             'HTML 4.01 Strict',
 6120:             false,
 6121:             array_merge($common, $non_xml),
 6122:             array('Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'),
 6123:             array(),
 6124:             '-//W3C//DTD HTML 4.01//EN',
 6125:             'http://www.w3.org/TR/html4/strict.dtd'
 6126:         );
 6127: 
 6128:         $this->doctypes->register(
 6129:             'XHTML 1.0 Transitional',
 6130:             true,
 6131:             array_merge($common, $transitional, $xml, $non_xml),
 6132:             array('Tidy_Transitional', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Name'),
 6133:             array(),
 6134:             '-//W3C//DTD XHTML 1.0 Transitional//EN',
 6135:             'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'
 6136:         );
 6137: 
 6138:         $this->doctypes->register(
 6139:             'XHTML 1.0 Strict',
 6140:             true,
 6141:             array_merge($common, $xml, $non_xml),
 6142:             array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'),
 6143:             array(),
 6144:             '-//W3C//DTD XHTML 1.0 Strict//EN',
 6145:             'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'
 6146:         );
 6147: 
 6148:         $this->doctypes->register(
 6149:             'XHTML 1.1',
 6150:             true,
 6151:             
 6152:             
 6153:             array_merge($common, $xml, array('Ruby', 'Iframe')),
 6154:             array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Strict', 'Tidy_Name'), 
 6155:             array(),
 6156:             '-//W3C//DTD XHTML 1.1//EN',
 6157:             'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'
 6158:         );
 6159: 
 6160:     }
 6161: 
 6162:      6163:  6164:  6165:  6166:  6167:  6168:  6169:  6170:  6171:  6172:  6173:  6174:  6175:  6176:  6177:  6178:  6179:  6180:  6181:  6182: 
 6183:     public function registerModule($module, $overload = false)
 6184:     {
 6185:         if (is_string($module)) {
 6186:             
 6187:             $original_module = $module;
 6188:             $ok = false;
 6189:             foreach ($this->prefixes as $prefix) {
 6190:                 $module = $prefix . $original_module;
 6191:                 if (class_exists($module)) {
 6192:                     $ok = true;
 6193:                     break;
 6194:                 }
 6195:             }
 6196:             if (!$ok) {
 6197:                 $module = $original_module;
 6198:                 if (!class_exists($module)) {
 6199:                     trigger_error(
 6200:                         $original_module . ' module does not exist',
 6201:                         E_USER_ERROR
 6202:                     );
 6203:                     return;
 6204:                 }
 6205:             }
 6206:             $module = new $module();
 6207:         }
 6208:         if (empty($module->name)) {
 6209:             trigger_error('Module instance of ' . get_class($module) . ' must have name');
 6210:             return;
 6211:         }
 6212:         if (!$overload && isset($this->registeredModules[$module->name])) {
 6213:             trigger_error('Overloading ' . $module->name . ' without explicit overload parameter', E_USER_WARNING);
 6214:         }
 6215:         $this->registeredModules[$module->name] = $module;
 6216:     }
 6217: 
 6218:      6219:  6220:  6221: 
 6222:     public function addModule($module)
 6223:     {
 6224:         $this->registerModule($module);
 6225:         if (is_object($module)) {
 6226:             $module = $module->name;
 6227:         }
 6228:         $this->userModules[] = $module;
 6229:     }
 6230: 
 6231:      6232:  6233:  6234: 
 6235:     public function addPrefix($prefix)
 6236:     {
 6237:         $this->prefixes[] = $prefix;
 6238:     }
 6239: 
 6240:      6241:  6242:  6243:  6244: 
 6245:     public function setup($config)
 6246:     {
 6247:         $this->trusted = $config->get('HTML.Trusted');
 6248: 
 6249:         
 6250:         $this->doctype = $this->doctypes->make($config);
 6251:         $modules = $this->doctype->modules;
 6252: 
 6253:         
 6254:         $lookup = $config->get('HTML.AllowedModules');
 6255:         $special_cases = $config->get('HTML.CoreModules');
 6256: 
 6257:         if (is_array($lookup)) {
 6258:             foreach ($modules as $k => $m) {
 6259:                 if (isset($special_cases[$m])) {
 6260:                     continue;
 6261:                 }
 6262:                 if (!isset($lookup[$m])) {
 6263:                     unset($modules[$k]);
 6264:                 }
 6265:             }
 6266:         }
 6267: 
 6268:         
 6269:         if ($config->get('HTML.Proprietary')) {
 6270:             $modules[] = 'Proprietary';
 6271:         }
 6272:         if ($config->get('HTML.SafeObject')) {
 6273:             $modules[] = 'SafeObject';
 6274:         }
 6275:         if ($config->get('HTML.SafeEmbed')) {
 6276:             $modules[] = 'SafeEmbed';
 6277:         }
 6278:         if ($config->get('HTML.SafeScripting') !== array()) {
 6279:             $modules[] = 'SafeScripting';
 6280:         }
 6281:         if ($config->get('HTML.Nofollow')) {
 6282:             $modules[] = 'Nofollow';
 6283:         }
 6284:         if ($config->get('HTML.TargetBlank')) {
 6285:             $modules[] = 'TargetBlank';
 6286:         }
 6287: 
 6288:         
 6289:         $modules = array_merge($modules, $this->userModules);
 6290: 
 6291:         foreach ($modules as $module) {
 6292:             $this->processModule($module);
 6293:             $this->modules[$module]->setup($config);
 6294:         }
 6295: 
 6296:         foreach ($this->doctype->tidyModules as $module) {
 6297:             $this->processModule($module);
 6298:             $this->modules[$module]->setup($config);
 6299:         }
 6300: 
 6301:         
 6302:         foreach ($this->modules as $module) {
 6303:             $n = array();
 6304:             foreach ($module->info_injector as $injector) {
 6305:                 if (!is_object($injector)) {
 6306:                     $class = "HTMLPurifier_Injector_$injector";
 6307:                     $injector = new $class;
 6308:                 }
 6309:                 $n[$injector->name] = $injector;
 6310:             }
 6311:             $module->info_injector = $n;
 6312:         }
 6313: 
 6314:         
 6315:         foreach ($this->modules as $module) {
 6316:             foreach ($module->info as $name => $def) {
 6317:                 if (!isset($this->elementLookup[$name])) {
 6318:                     $this->elementLookup[$name] = array();
 6319:                 }
 6320:                 $this->elementLookup[$name][] = $module->name;
 6321:             }
 6322:         }
 6323: 
 6324:         
 6325:         $this->contentSets = new HTMLPurifier_ContentSets(
 6326:             
 6327:             
 6328:             $this->modules
 6329:         );
 6330:         $this->attrCollections = new HTMLPurifier_AttrCollections(
 6331:             $this->attrTypes,
 6332:             
 6333:             
 6334:             
 6335:             $this->modules
 6336:         );
 6337:     }
 6338: 
 6339:      6340:  6341:  6342: 
 6343:     public function processModule($module)
 6344:     {
 6345:         if (!isset($this->registeredModules[$module]) || is_object($module)) {
 6346:             $this->registerModule($module);
 6347:         }
 6348:         $this->modules[$module] = $this->registeredModules[$module];
 6349:     }
 6350: 
 6351:      6352:  6353:  6354: 
 6355:     public function getElements()
 6356:     {
 6357:         $elements = array();
 6358:         foreach ($this->modules as $module) {
 6359:             if (!$this->trusted && !$module->safe) {
 6360:                 continue;
 6361:             }
 6362:             foreach ($module->info as $name => $v) {
 6363:                 if (isset($elements[$name])) {
 6364:                     continue;
 6365:                 }
 6366:                 $elements[$name] = $this->getElement($name);
 6367:             }
 6368:         }
 6369: 
 6370:         
 6371:         
 6372:         foreach ($elements as $n => $v) {
 6373:             if ($v === false) {
 6374:                 unset($elements[$n]);
 6375:             }
 6376:         }
 6377: 
 6378:         return $elements;
 6379: 
 6380:     }
 6381: 
 6382:      6383:  6384:  6385:  6386:  6387:  6388:  6389:  6390:  6391: 
 6392:     public function getElement($name, $trusted = null)
 6393:     {
 6394:         if (!isset($this->elementLookup[$name])) {
 6395:             return false;
 6396:         }
 6397: 
 6398:         
 6399:         $def = false;
 6400:         if ($trusted === null) {
 6401:             $trusted = $this->trusted;
 6402:         }
 6403: 
 6404:         
 6405:         
 6406:         foreach ($this->elementLookup[$name] as $module_name) {
 6407:             $module = $this->modules[$module_name];
 6408: 
 6409:             
 6410:             
 6411:             if (!$trusted && !$module->safe) {
 6412:                 continue;
 6413:             }
 6414: 
 6415:             
 6416:             
 6417:             
 6418:             $new_def = clone $module->info[$name];
 6419: 
 6420:             if (!$def && $new_def->standalone) {
 6421:                 $def = $new_def;
 6422:             } elseif ($def) {
 6423:                 
 6424:                 
 6425:                 $def->mergeIn($new_def);
 6426:             } else {
 6427:                 
 6428:                 
 6429:                 
 6430:                 
 6431:                 
 6432:                 
 6433:                 
 6434:                 
 6435:                 
 6436:                 
 6437:                 continue;
 6438:             }
 6439: 
 6440:             
 6441:             $this->attrCollections->performInclusions($def->attr);
 6442:             $this->attrCollections->expandIdentifiers($def->attr, $this->attrTypes);
 6443: 
 6444:             
 6445:             if (is_string($def->content_model) &&
 6446:                 strpos($def->content_model, 'Inline') !== false) {
 6447:                 if ($name != 'del' && $name != 'ins') {
 6448:                     
 6449:                     $def->descendants_are_inline = true;
 6450:                 }
 6451:             }
 6452: 
 6453:             $this->contentSets->generateChildDef($def, $module);
 6454:         }
 6455: 
 6456:         
 6457:         
 6458:         if (!$def) {
 6459:             return false;
 6460:         }
 6461: 
 6462:         
 6463:         foreach ($def->attr as $attr_name => $attr_def) {
 6464:             if ($attr_def->required) {
 6465:                 $def->required_attr[] = $attr_name;
 6466:             }
 6467:         }
 6468:         return $def;
 6469:     }
 6470: }
 6471: 
 6472: 
 6473: 
 6474: 
 6475: 
 6476:  6477:  6478:  6479:  6480:  6481: 
 6482: class HTMLPurifier_IDAccumulator
 6483: {
 6484: 
 6485:      6486:  6487:  6488: 
 6489:     public $ids = array();
 6490: 
 6491:      6492:  6493:  6494:  6495:  6496: 
 6497:     public static function build($config, $context)
 6498:     {
 6499:         $id_accumulator = new HTMLPurifier_IDAccumulator();
 6500:         $id_accumulator->load($config->get('Attr.IDBlacklist'));
 6501:         return $id_accumulator;
 6502:     }
 6503: 
 6504:      6505:  6506:  6507:  6508: 
 6509:     public function add($id)
 6510:     {
 6511:         if (isset($this->ids[$id])) {
 6512:             return false;
 6513:         }
 6514:         return $this->ids[$id] = true;
 6515:     }
 6516: 
 6517:      6518:  6519:  6520:  6521: 
 6522:     public function load($array_of_ids)
 6523:     {
 6524:         foreach ($array_of_ids as $id) {
 6525:             $this->ids[$id] = true;
 6526:         }
 6527:     }
 6528: }
 6529: 
 6530: 
 6531: 
 6532: 
 6533: 
 6534:  6535:  6536:  6537:  6538:  6539:  6540:  6541:  6542:  6543:  6544:  6545:  6546: 
 6547: abstract class HTMLPurifier_Injector
 6548: {
 6549: 
 6550:      6551:  6552:  6553: 
 6554:     public $name;
 6555: 
 6556:      6557:  6558: 
 6559:     protected $htmlDefinition;
 6560: 
 6561:      6562:  6563:  6564:  6565: 
 6566:     protected $currentNesting;
 6567: 
 6568:      6569:  6570:  6571: 
 6572:     protected $currentToken;
 6573: 
 6574:      6575:  6576:  6577: 
 6578:     protected $inputZipper;
 6579: 
 6580:      6581:  6582:  6583:  6584:  6585: 
 6586:     public $needed = array();
 6587: 
 6588:      6589:  6590:  6591: 
 6592:     protected $rewindOffset = false;
 6593: 
 6594:      6595:  6596:  6597:  6598:  6599:  6600:  6601:  6602: 
 6603:     public function rewindOffset($offset)
 6604:     {
 6605:         $this->rewindOffset = $offset;
 6606:     }
 6607: 
 6608:      6609:  6610:  6611: 
 6612:     public function getRewindOffset()
 6613:     {
 6614:         $r = $this->rewindOffset;
 6615:         $this->rewindOffset = false;
 6616:         return $r;
 6617:     }
 6618: 
 6619:      6620:  6621:  6622:  6623:  6624:  6625:  6626:  6627: 
 6628:     public function prepare($config, $context)
 6629:     {
 6630:         $this->htmlDefinition = $config->getHTMLDefinition();
 6631:         
 6632:         
 6633:         
 6634:         $result = $this->checkNeeded($config);
 6635:         if ($result !== false) {
 6636:             return $result;
 6637:         }
 6638:         $this->currentNesting =& $context->get('CurrentNesting');
 6639:         $this->currentToken   =& $context->get('CurrentToken');
 6640:         $this->inputZipper    =& $context->get('InputZipper');
 6641:         return false;
 6642:     }
 6643: 
 6644:      6645:  6646:  6647:  6648:  6649:  6650: 
 6651:     public function checkNeeded($config)
 6652:     {
 6653:         $def = $config->getHTMLDefinition();
 6654:         foreach ($this->needed as $element => $attributes) {
 6655:             if (is_int($element)) {
 6656:                 $element = $attributes;
 6657:             }
 6658:             if (!isset($def->info[$element])) {
 6659:                 return $element;
 6660:             }
 6661:             if (!is_array($attributes)) {
 6662:                 continue;
 6663:             }
 6664:             foreach ($attributes as $name) {
 6665:                 if (!isset($def->info[$element]->attr[$name])) {
 6666:                     return "$element.$name";
 6667:                 }
 6668:             }
 6669:         }
 6670:         return false;
 6671:     }
 6672: 
 6673:      6674:  6675:  6676:  6677: 
 6678:     public function allowsElement($name)
 6679:     {
 6680:         if (!empty($this->currentNesting)) {
 6681:             $parent_token = array_pop($this->currentNesting);
 6682:             $this->currentNesting[] = $parent_token;
 6683:             $parent = $this->htmlDefinition->info[$parent_token->name];
 6684:         } else {
 6685:             $parent = $this->htmlDefinition->info_parent_def;
 6686:         }
 6687:         if (!isset($parent->child->elements[$name]) || isset($parent->excludes[$name])) {
 6688:             return false;
 6689:         }
 6690:         
 6691:         for ($i = count($this->currentNesting) - 2; $i >= 0; $i--) {
 6692:             $node = $this->currentNesting[$i];
 6693:             $def  = $this->htmlDefinition->info[$node->name];
 6694:             if (isset($def->excludes[$name])) {
 6695:                 return false;
 6696:             }
 6697:         }
 6698:         return true;
 6699:     }
 6700: 
 6701:      6702:  6703:  6704:  6705:  6706:  6707:  6708:  6709:  6710: 
 6711:     protected function forward(&$i, &$current)
 6712:     {
 6713:         if ($i === null) {
 6714:             $i = count($this->inputZipper->back) - 1;
 6715:         } else {
 6716:             $i--;
 6717:         }
 6718:         if ($i < 0) {
 6719:             return false;
 6720:         }
 6721:         $current = $this->inputZipper->back[$i];
 6722:         return true;
 6723:     }
 6724: 
 6725:      6726:  6727:  6728:  6729:  6730:  6731:  6732:  6733:  6734: 
 6735:     protected function forwardUntilEndToken(&$i, &$current, &$nesting)
 6736:     {
 6737:         $result = $this->forward($i, $current);
 6738:         if (!$result) {
 6739:             return false;
 6740:         }
 6741:         if ($nesting === null) {
 6742:             $nesting = 0;
 6743:         }
 6744:         if ($current instanceof HTMLPurifier_Token_Start) {
 6745:             $nesting++;
 6746:         } elseif ($current instanceof HTMLPurifier_Token_End) {
 6747:             if ($nesting <= 0) {
 6748:                 return false;
 6749:             }
 6750:             $nesting--;
 6751:         }
 6752:         return true;
 6753:     }
 6754: 
 6755:      6756:  6757:  6758:  6759:  6760:  6761:  6762:  6763:  6764: 
 6765:     protected function backward(&$i, &$current)
 6766:     {
 6767:         if ($i === null) {
 6768:             $i = count($this->inputZipper->front) - 1;
 6769:         } else {
 6770:             $i--;
 6771:         }
 6772:         if ($i < 0) {
 6773:             return false;
 6774:         }
 6775:         $current = $this->inputZipper->front[$i];
 6776:         return true;
 6777:     }
 6778: 
 6779:      6780:  6781: 
 6782:     public function handleText(&$token)
 6783:     {
 6784:     }
 6785: 
 6786:      6787:  6788: 
 6789:     public function handleElement(&$token)
 6790:     {
 6791:     }
 6792: 
 6793:      6794:  6795: 
 6796:     public function handleEnd(&$token)
 6797:     {
 6798:         $this->notifyEnd($token);
 6799:     }
 6800: 
 6801:      6802:  6803:  6804:  6805:  6806: 
 6807:     public function notifyEnd($token)
 6808:     {
 6809:     }
 6810: }
 6811: 
 6812: 
 6813: 
 6814: 
 6815: 
 6816:  6817:  6818:  6819: 
 6820: class HTMLPurifier_Language
 6821: {
 6822: 
 6823:      6824:  6825:  6826: 
 6827:     public $code = 'en';
 6828: 
 6829:      6830:  6831:  6832: 
 6833:     public $fallback = false;
 6834: 
 6835:      6836:  6837:  6838: 
 6839:     public $messages = array();
 6840: 
 6841:      6842:  6843:  6844: 
 6845:     public $errorNames = array();
 6846: 
 6847:      6848:  6849:  6850:  6851:  6852: 
 6853:     public $error = false;
 6854: 
 6855:      6856:  6857:  6858:  6859: 
 6860:     public $_loaded = false;
 6861: 
 6862:      6863:  6864: 
 6865:     protected $config;
 6866: 
 6867:      6868:  6869: 
 6870:     protected $context;
 6871: 
 6872:      6873:  6874:  6875: 
 6876:     public function __construct($config, $context)
 6877:     {
 6878:         $this->config  = $config;
 6879:         $this->context = $context;
 6880:     }
 6881: 
 6882:      6883:  6884:  6885: 
 6886:     public function load()
 6887:     {
 6888:         if ($this->_loaded) {
 6889:             return;
 6890:         }
 6891:         $factory = HTMLPurifier_LanguageFactory::instance();
 6892:         $factory->loadLanguage($this->code);
 6893:         foreach ($factory->keys as $key) {
 6894:             $this->$key = $factory->cache[$this->code][$key];
 6895:         }
 6896:         $this->_loaded = true;
 6897:     }
 6898: 
 6899:      6900:  6901:  6902:  6903: 
 6904:     public function getMessage($key)
 6905:     {
 6906:         if (!$this->_loaded) {
 6907:             $this->load();
 6908:         }
 6909:         if (!isset($this->messages[$key])) {
 6910:             return "[$key]";
 6911:         }
 6912:         return $this->messages[$key];
 6913:     }
 6914: 
 6915:      6916:  6917:  6918:  6919: 
 6920:     public function getErrorName($int)
 6921:     {
 6922:         if (!$this->_loaded) {
 6923:             $this->load();
 6924:         }
 6925:         if (!isset($this->errorNames[$int])) {
 6926:             return "[Error: $int]";
 6927:         }
 6928:         return $this->errorNames[$int];
 6929:     }
 6930: 
 6931:      6932:  6933:  6934:  6935: 
 6936:     public function listify($array)
 6937:     {
 6938:         $sep      = $this->getMessage('Item separator');
 6939:         $sep_last = $this->getMessage('Item separator last');
 6940:         $ret = '';
 6941:         for ($i = 0, $c = count($array); $i < $c; $i++) {
 6942:             if ($i == 0) {
 6943:             } elseif ($i + 1 < $c) {
 6944:                 $ret .= $sep;
 6945:             } else {
 6946:                 $ret .= $sep_last;
 6947:             }
 6948:             $ret .= $array[$i];
 6949:         }
 6950:         return $ret;
 6951:     }
 6952: 
 6953:      6954:  6955:  6956:  6957:  6958:  6959:  6960: 
 6961:     public function formatMessage($key, $args = array())
 6962:     {
 6963:         if (!$this->_loaded) {
 6964:             $this->load();
 6965:         }
 6966:         if (!isset($this->messages[$key])) {
 6967:             return "[$key]";
 6968:         }
 6969:         $raw = $this->messages[$key];
 6970:         $subst = array();
 6971:         $generator = false;
 6972:         foreach ($args as $i => $value) {
 6973:             if (is_object($value)) {
 6974:                 if ($value instanceof HTMLPurifier_Token) {
 6975:                     
 6976:                     if (!$generator) {
 6977:                         $generator = $this->context->get('Generator');
 6978:                     }
 6979:                     if (isset($value->name)) {
 6980:                         $subst['$'.$i.'.Name'] = $value->name;
 6981:                     }
 6982:                     if (isset($value->data)) {
 6983:                         $subst['$'.$i.'.Data'] = $value->data;
 6984:                     }
 6985:                     $subst['$'.$i.'.Compact'] =
 6986:                     $subst['$'.$i.'.Serialized'] = $generator->generateFromToken($value);
 6987:                     
 6988:                     
 6989:                     
 6990:                     if (!empty($value->attr)) {
 6991:                         $stripped_token = clone $value;
 6992:                         $stripped_token->attr = array();
 6993:                         $subst['$'.$i.'.Compact'] = $generator->generateFromToken($stripped_token);
 6994:                     }
 6995:                     $subst['$'.$i.'.Line'] = $value->line ? $value->line : 'unknown';
 6996:                 }
 6997:                 continue;
 6998:             } elseif (is_array($value)) {
 6999:                 $keys = array_keys($value);
 7000:                 if (array_keys($keys) === $keys) {
 7001:                     
 7002:                     $subst['$'.$i] = $this->listify($value);
 7003:                 } else {
 7004:                     
 7005:                     
 7006:                     $subst['$'.$i.'.Keys'] = $this->listify($keys);
 7007:                     $subst['$'.$i.'.Values'] = $this->listify(array_values($value));
 7008:                 }
 7009:                 continue;
 7010:             }
 7011:             $subst['$' . $i] = $value;
 7012:         }
 7013:         return strtr($raw, $subst);
 7014:     }
 7015: }
 7016: 
 7017: 
 7018: 
 7019: 
 7020: 
 7021:  7022:  7023:  7024:  7025:  7026:  7027: 
 7028: class HTMLPurifier_LanguageFactory
 7029: {
 7030: 
 7031:      7032:  7033:  7034:  7035: 
 7036:     public $cache;
 7037: 
 7038:      7039:  7040:  7041:  7042: 
 7043:     public $keys = array('fallback', 'messages', 'errorNames');
 7044: 
 7045:      7046:  7047:  7048:  7049: 
 7050:     protected $validator;
 7051: 
 7052:      7053:  7054:  7055:  7056: 
 7057:     protected $dir;
 7058: 
 7059:      7060:  7061:  7062: 
 7063:     protected $mergeable_keys_map = array('messages' => true, 'errorNames' => true);
 7064: 
 7065:      7066:  7067:  7068: 
 7069:     protected $mergeable_keys_list = array();
 7070: 
 7071:      7072:  7073:  7074:  7075:  7076: 
 7077:     public static function instance($prototype = null)
 7078:     {
 7079:         static $instance = null;
 7080:         if ($prototype !== null) {
 7081:             $instance = $prototype;
 7082:         } elseif ($instance === null || $prototype == true) {
 7083:             $instance = new HTMLPurifier_LanguageFactory();
 7084:             $instance->setup();
 7085:         }
 7086:         return $instance;
 7087:     }
 7088: 
 7089:      7090:  7091:  7092: 
 7093:     public function setup()
 7094:     {
 7095:         $this->validator = new HTMLPurifier_AttrDef_Lang();
 7096:         $this->dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier';
 7097:     }
 7098: 
 7099:      7100:  7101:  7102:  7103:  7104:  7105: 
 7106:     public function create($config, $context, $code = false)
 7107:     {
 7108:         
 7109:         if ($code === false) {
 7110:             $code = $this->validator->validate(
 7111:                 $config->get('Core.Language'),
 7112:                 $config,
 7113:                 $context
 7114:             );
 7115:         } else {
 7116:             $code = $this->validator->validate($code, $config, $context);
 7117:         }
 7118:         if ($code === false) {
 7119:             $code = 'en'; 
 7120:         }
 7121: 
 7122:         $pcode = str_replace('-', '_', $code); 
 7123:         static $depth = 0; 
 7124: 
 7125:         if ($code == 'en') {
 7126:             $lang = new HTMLPurifier_Language($config, $context);
 7127:         } else {
 7128:             $class = 'HTMLPurifier_Language_' . $pcode;
 7129:             $file  = $this->dir . '/Language/classes/' . $code . '.php';
 7130:             if (file_exists($file) || class_exists($class, false)) {
 7131:                 $lang = new $class($config, $context);
 7132:             } else {
 7133:                 
 7134:                 $raw_fallback = $this->getFallbackFor($code);
 7135:                 $fallback = $raw_fallback ? $raw_fallback : 'en';
 7136:                 $depth++;
 7137:                 $lang = $this->create($config, $context, $fallback);
 7138:                 if (!$raw_fallback) {
 7139:                     $lang->error = true;
 7140:                 }
 7141:                 $depth--;
 7142:             }
 7143:         }
 7144:         $lang->code = $code;
 7145:         return $lang;
 7146:     }
 7147: 
 7148:      7149:  7150:  7151:  7152:  7153: 
 7154:     public function getFallbackFor($code)
 7155:     {
 7156:         $this->loadLanguage($code);
 7157:         return $this->cache[$code]['fallback'];
 7158:     }
 7159: 
 7160:      7161:  7162:  7163: 
 7164:     public function loadLanguage($code)
 7165:     {
 7166:         static $languages_seen = array(); 
 7167: 
 7168:         
 7169:         if (isset($this->cache[$code])) {
 7170:             return;
 7171:         }
 7172: 
 7173:         
 7174:         $filename = $this->dir . '/Language/messages/' . $code . '.php';
 7175: 
 7176:         
 7177:         $fallback = ($code != 'en') ? 'en' : false;
 7178: 
 7179:         
 7180:         if (!file_exists($filename)) {
 7181:             
 7182:             $filename = $this->dir . '/Language/messages/en.php';
 7183:             $cache = array();
 7184:         } else {
 7185:             include $filename;
 7186:             $cache = compact($this->keys);
 7187:         }
 7188: 
 7189:         
 7190:         if (!empty($fallback)) {
 7191: 
 7192:             
 7193:             if (isset($languages_seen[$code])) {
 7194:                 trigger_error(
 7195:                     'Circular fallback reference in language ' .
 7196:                     $code,
 7197:                     E_USER_ERROR
 7198:                 );
 7199:                 $fallback = 'en';
 7200:             }
 7201:             $language_seen[$code] = true;
 7202: 
 7203:             
 7204:             $this->loadLanguage($fallback);
 7205:             $fallback_cache = $this->cache[$fallback];
 7206: 
 7207:             
 7208:             foreach ($this->keys as $key) {
 7209:                 if (isset($cache[$key]) && isset($fallback_cache[$key])) {
 7210:                     if (isset($this->mergeable_keys_map[$key])) {
 7211:                         $cache[$key] = $cache[$key] + $fallback_cache[$key];
 7212:                     } elseif (isset($this->mergeable_keys_list[$key])) {
 7213:                         $cache[$key] = array_merge($fallback_cache[$key], $cache[$key]);
 7214:                     }
 7215:                 } else {
 7216:                     $cache[$key] = $fallback_cache[$key];
 7217:                 }
 7218:             }
 7219:         }
 7220: 
 7221:         
 7222:         $this->cache[$code] = $cache;
 7223:         return;
 7224:     }
 7225: }
 7226: 
 7227: 
 7228: 
 7229: 
 7230: 
 7231:  7232:  7233:  7234: 
 7235: class HTMLPurifier_Length
 7236: {
 7237: 
 7238:      7239:  7240:  7241: 
 7242:     protected $n;
 7243: 
 7244:      7245:  7246:  7247: 
 7248:     protected $unit;
 7249: 
 7250:      7251:  7252:  7253: 
 7254:     protected $isValid;
 7255: 
 7256:      7257:  7258:  7259: 
 7260:     protected static $allowedUnits = array(
 7261:         'em' => true, 'ex' => true, 'px' => true, 'in' => true,
 7262:         'cm' => true, 'mm' => true, 'pt' => true, 'pc' => true
 7263:     );
 7264: 
 7265:      7266:  7267:  7268: 
 7269:     public function __construct($n = '0', $u = false)
 7270:     {
 7271:         $this->n = (string) $n;
 7272:         $this->unit = $u !== false ? (string) $u : false;
 7273:     }
 7274: 
 7275:      7276:  7277:  7278:  7279: 
 7280:     public static function make($s)
 7281:     {
 7282:         if ($s instanceof HTMLPurifier_Length) {
 7283:             return $s;
 7284:         }
 7285:         $n_length = strspn($s, '1234567890.+-');
 7286:         $n = substr($s, 0, $n_length);
 7287:         $unit = substr($s, $n_length);
 7288:         if ($unit === '') {
 7289:             $unit = false;
 7290:         }
 7291:         return new HTMLPurifier_Length($n, $unit);
 7292:     }
 7293: 
 7294:      7295:  7296:  7297: 
 7298:     protected function validate()
 7299:     {
 7300:         
 7301:         if ($this->n === '+0' || $this->n === '-0') {
 7302:             $this->n = '0';
 7303:         }
 7304:         if ($this->n === '0' && $this->unit === false) {
 7305:             return true;
 7306:         }
 7307:         if (!ctype_lower($this->unit)) {
 7308:             $this->unit = strtolower($this->unit);
 7309:         }
 7310:         if (!isset(HTMLPurifier_Length::$allowedUnits[$this->unit])) {
 7311:             return false;
 7312:         }
 7313:         
 7314:         $def = new HTMLPurifier_AttrDef_CSS_Number();
 7315:         $result = $def->validate($this->n, false, false);
 7316:         if ($result === false) {
 7317:             return false;
 7318:         }
 7319:         $this->n = $result;
 7320:         return true;
 7321:     }
 7322: 
 7323:      7324:  7325:  7326: 
 7327:     public function toString()
 7328:     {
 7329:         if (!$this->isValid()) {
 7330:             return false;
 7331:         }
 7332:         return $this->n . $this->unit;
 7333:     }
 7334: 
 7335:      7336:  7337:  7338: 
 7339:     public function getN()
 7340:     {
 7341:         return $this->n;
 7342:     }
 7343: 
 7344:      7345:  7346:  7347: 
 7348:     public function getUnit()
 7349:     {
 7350:         return $this->unit;
 7351:     }
 7352: 
 7353:      7354:  7355:  7356: 
 7357:     public function isValid()
 7358:     {
 7359:         if ($this->isValid === null) {
 7360:             $this->isValid = $this->validate();
 7361:         }
 7362:         return $this->isValid;
 7363:     }
 7364: 
 7365:      7366:  7367:  7368:  7369:  7370:  7371: 
 7372:     public function compareTo($l)
 7373:     {
 7374:         if ($l === false) {
 7375:             return false;
 7376:         }
 7377:         if ($l->unit !== $this->unit) {
 7378:             $converter = new HTMLPurifier_UnitConverter();
 7379:             $l = $converter->convert($l, $this->unit);
 7380:             if ($l === false) {
 7381:                 return false;
 7382:             }
 7383:         }
 7384:         return $this->n - $l->n;
 7385:     }
 7386: }
 7387: 
 7388: 
 7389: 
 7390: 
 7391: 
 7392:  7393:  7394:  7395:  7396:  7397:  7398:  7399:  7400:  7401:  7402:  7403:  7404:  7405:  7406:  7407:  7408:  7409:  7410:  7411:  7412:  7413:  7414:  7415:  7416:  7417:  7418:  7419:  7420:  7421:  7422:  7423:  7424:  7425:  7426:  7427:  7428:  7429:  7430: 
 7431: class HTMLPurifier_Lexer
 7432: {
 7433: 
 7434:      7435:  7436:  7437: 
 7438:     public $tracksLineNumbers = false;
 7439: 
 7440:     
 7441: 
 7442:      7443:  7444:  7445:  7446:  7447:  7448:  7449:  7450:  7451:  7452:  7453:  7454:  7455:  7456:  7457: 
 7458:     public static function create($config)
 7459:     {
 7460:         if (!($config instanceof HTMLPurifier_Config)) {
 7461:             $lexer = $config;
 7462:             trigger_error(
 7463:                 "Passing a prototype to
 7464:                 HTMLPurifier_Lexer::create() is deprecated, please instead
 7465:                 use %Core.LexerImpl",
 7466:                 E_USER_WARNING
 7467:             );
 7468:         } else {
 7469:             $lexer = $config->get('Core.LexerImpl');
 7470:         }
 7471: 
 7472:         $needs_tracking =
 7473:             $config->get('Core.MaintainLineNumbers') ||
 7474:             $config->get('Core.CollectErrors');
 7475: 
 7476:         $inst = null;
 7477:         if (is_object($lexer)) {
 7478:             $inst = $lexer;
 7479:         } else {
 7480:             if (is_null($lexer)) {
 7481:                 do {
 7482:                     
 7483:                     if ($needs_tracking) {
 7484:                         $lexer = 'DirectLex';
 7485:                         break;
 7486:                     }
 7487: 
 7488:                     if (class_exists('DOMDocument') &&
 7489:                         method_exists('DOMDocument', 'loadHTML') &&
 7490:                         !extension_loaded('domxml')
 7491:                     ) {
 7492:                         
 7493:                         
 7494:                         
 7495:                         
 7496:                         $lexer = 'DOMLex';
 7497:                     } else {
 7498:                         $lexer = 'DirectLex';
 7499:                     }
 7500:                 } while (0);
 7501:             } 
 7502: 
 7503:             
 7504:             switch ($lexer) {
 7505:                 case 'DOMLex':
 7506:                     $inst = new HTMLPurifier_Lexer_DOMLex();
 7507:                     break;
 7508:                 case 'DirectLex':
 7509:                     $inst = new HTMLPurifier_Lexer_DirectLex();
 7510:                     break;
 7511:                 case 'PH5P':
 7512:                     $inst = new HTMLPurifier_Lexer_PH5P();
 7513:                     break;
 7514:                 default:
 7515:                     throw new HTMLPurifier_Exception(
 7516:                         "Cannot instantiate unrecognized Lexer type " .
 7517:                         htmlspecialchars($lexer)
 7518:                     );
 7519:             }
 7520:         }
 7521: 
 7522:         if (!$inst) {
 7523:             throw new HTMLPurifier_Exception('No lexer was instantiated');
 7524:         }
 7525: 
 7526:         
 7527:         
 7528:         if ($needs_tracking && !$inst->tracksLineNumbers) {
 7529:             throw new HTMLPurifier_Exception(
 7530:                 'Cannot use lexer that does not support line numbers with ' .
 7531:                 'Core.MaintainLineNumbers or Core.CollectErrors (use DirectLex instead)'
 7532:             );
 7533:         }
 7534: 
 7535:         return $inst;
 7536: 
 7537:     }
 7538: 
 7539:     
 7540: 
 7541:     public function __construct()
 7542:     {
 7543:         $this->_entity_parser = new HTMLPurifier_EntityParser();
 7544:     }
 7545: 
 7546:      7547:  7548:  7549: 
 7550:     protected $_special_entity2str =
 7551:         array(
 7552:             '"' => '"',
 7553:             '&' => '&',
 7554:             '<' => '<',
 7555:             '>' => '>',
 7556:             ''' => "'",
 7557:             ''' => "'",
 7558:             ''' => "'"
 7559:         );
 7560: 
 7561:      7562:  7563:  7564:  7565:  7566:  7567:  7568:  7569:  7570:  7571:  7572:  7573:  7574: 
 7575:     public function parseData($string)
 7576:     {
 7577:         
 7578:         if ($string === '') {
 7579:             return '';
 7580:         }
 7581: 
 7582:         
 7583:         $num_amp = substr_count($string, '&') - substr_count($string, '& ') -
 7584:             ($string[strlen($string) - 1] === '&' ? 1 : 0);
 7585: 
 7586:         if (!$num_amp) {
 7587:             return $string;
 7588:         } 
 7589:         $num_esc_amp = substr_count($string, '&');
 7590:         $string = strtr($string, $this->_special_entity2str);
 7591: 
 7592:         
 7593:         $num_amp_2 = substr_count($string, '&') - substr_count($string, '& ') -
 7594:             ($string[strlen($string) - 1] === '&' ? 1 : 0);
 7595: 
 7596:         if ($num_amp_2 <= $num_esc_amp) {
 7597:             return $string;
 7598:         }
 7599: 
 7600:         
 7601:         $string = $this->_entity_parser->substituteSpecialEntities($string);
 7602:         return $string;
 7603:     }
 7604: 
 7605:      7606:  7607:  7608:  7609:  7610:  7611: 
 7612:     public function tokenizeHTML($string, $config, $context)
 7613:     {
 7614:         trigger_error('Call to abstract class', E_USER_ERROR);
 7615:     }
 7616: 
 7617:      7618:  7619:  7620:  7621: 
 7622:     protected static function escapeCDATA($string)
 7623:     {
 7624:         return preg_replace_callback(
 7625:             '/<!\[CDATA\[(.+?)\]\]>/s',
 7626:             array('HTMLPurifier_Lexer', 'CDATACallback'),
 7627:             $string
 7628:         );
 7629:     }
 7630: 
 7631:      7632:  7633:  7634:  7635: 
 7636:     protected static function ($string)
 7637:     {
 7638:         return preg_replace_callback(
 7639:             '#<!--//--><!\[CDATA\[//><!--(.+?)//--><!\]\]>#s',
 7640:             array('HTMLPurifier_Lexer', 'CDATACallback'),
 7641:             $string
 7642:         );
 7643:     }
 7644: 
 7645:      7646:  7647:  7648:  7649: 
 7650:     protected static function removeIEConditional($string)
 7651:     {
 7652:         return preg_replace(
 7653:             '#<!--\[if [^>]+\]>.*?<!\[endif\]-->#si', 
 7654:             '',
 7655:             $string
 7656:         );
 7657:     }
 7658: 
 7659:      7660:  7661:  7662:  7663:  7664:  7665:  7666:  7667: 
 7668:     protected static function CDATACallback($matches)
 7669:     {
 7670:         
 7671:         return htmlspecialchars($matches[1], ENT_COMPAT, 'UTF-8');
 7672:     }
 7673: 
 7674:      7675:  7676:  7677:  7678:  7679:  7680:  7681:  7682: 
 7683:     public function normalize($html, $config, $context)
 7684:     {
 7685:         
 7686:         if ($config->get('Core.NormalizeNewlines')) {
 7687:             $html = str_replace("\r\n", "\n", $html);
 7688:             $html = str_replace("\r", "\n", $html);
 7689:         }
 7690: 
 7691:         if ($config->get('HTML.Trusted')) {
 7692:             
 7693:             $html = $this->escapeCommentedCDATA($html);
 7694:         }
 7695: 
 7696:         
 7697:         $html = $this->escapeCDATA($html);
 7698: 
 7699:         $html = $this->removeIEConditional($html);
 7700: 
 7701:         
 7702:         if ($config->get('Core.ConvertDocumentToFragment')) {
 7703:             $e = false;
 7704:             if ($config->get('Core.CollectErrors')) {
 7705:                 $e =& $context->get('ErrorCollector');
 7706:             }
 7707:             $new_html = $this->extractBody($html);
 7708:             if ($e && $new_html != $html) {
 7709:                 $e->send(E_WARNING, 'Lexer: Extracted body');
 7710:             }
 7711:             $html = $new_html;
 7712:         }
 7713: 
 7714:         
 7715:         $html = $this->_entity_parser->substituteNonSpecialEntities($html);
 7716: 
 7717:         
 7718:         
 7719:         
 7720:         $html = HTMLPurifier_Encoder::cleanUTF8($html);
 7721: 
 7722:         
 7723:         if ($config->get('Core.RemoveProcessingInstructions')) {
 7724:             $html = preg_replace('#<\?.+?\?>#s', '', $html);
 7725:         }
 7726: 
 7727:         return $html;
 7728:     }
 7729: 
 7730:      7731:  7732:  7733: 
 7734:     public function extractBody($html)
 7735:     {
 7736:         $matches = array();
 7737:         $result = preg_match('!<body[^>]*>(.*)</body>!is', $html, $matches);
 7738:         if ($result) {
 7739:             return $matches[1];
 7740:         } else {
 7741:             return $html;
 7742:         }
 7743:     }
 7744: }
 7745: 
 7746: 
 7747: 
 7748: 
 7749: 
 7750:  7751:  7752:  7753:  7754:  7755:  7756:  7757: 
 7758: abstract class HTMLPurifier_Node
 7759: {
 7760:      7761:  7762:  7763: 
 7764:     public $line;
 7765: 
 7766:      7767:  7768:  7769: 
 7770:     public $col;
 7771: 
 7772:      7773:  7774:  7775:  7776: 
 7777:     public $armor = array();
 7778: 
 7779:      7780:  7781:  7782:  7783:  7784:  7785: 
 7786:     public $dead = false;
 7787: 
 7788:      7789:  7790:  7791:  7792: 
 7793:     abstract public function toTokenPair();
 7794: }
 7795: 
 7796: 
 7797: 
 7798: 
 7799: 
 7800:  7801:  7802:  7803:  7804:  7805:  7806:  7807: 
 7808: class HTMLPurifier_PercentEncoder
 7809: {
 7810: 
 7811:      7812:  7813:  7814: 
 7815:     protected $preserve = array();
 7816: 
 7817:      7818:  7819:  7820: 
 7821:     public function __construct($preserve = false)
 7822:     {
 7823:         
 7824:         for ($i = 48; $i <= 57; $i++) { 
 7825:             $this->preserve[$i] = true;
 7826:         }
 7827:         for ($i = 65; $i <= 90; $i++) { 
 7828:             $this->preserve[$i] = true;
 7829:         }
 7830:         for ($i = 97; $i <= 122; $i++) { 
 7831:             $this->preserve[$i] = true;
 7832:         }
 7833:         $this->preserve[45] = true; 
 7834:         $this->preserve[46] = true; 
 7835:         $this->preserve[95] = true; 
 7836:         $this->preserve[126]= true; 
 7837: 
 7838:         
 7839:         if ($preserve !== false) {
 7840:             for ($i = 0, $c = strlen($preserve); $i < $c; $i++) {
 7841:                 $this->preserve[ord($preserve[$i])] = true;
 7842:             }
 7843:         }
 7844:     }
 7845: 
 7846:      7847:  7848:  7849:  7850:  7851:  7852:  7853:  7854:  7855: 
 7856:     public function encode($string)
 7857:     {
 7858:         $ret = '';
 7859:         for ($i = 0, $c = strlen($string); $i < $c; $i++) {
 7860:             if ($string[$i] !== '%' && !isset($this->preserve[$int = ord($string[$i])])) {
 7861:                 $ret .= '%' . sprintf('%02X', $int);
 7862:             } else {
 7863:                 $ret .= $string[$i];
 7864:             }
 7865:         }
 7866:         return $ret;
 7867:     }
 7868: 
 7869:      7870:  7871:  7872:  7873:  7874:  7875:  7876: 
 7877:     public function normalize($string)
 7878:     {
 7879:         if ($string == '') {
 7880:             return '';
 7881:         }
 7882:         $parts = explode('%', $string);
 7883:         $ret = array_shift($parts);
 7884:         foreach ($parts as $part) {
 7885:             $length = strlen($part);
 7886:             if ($length < 2) {
 7887:                 $ret .= '%25' . $part;
 7888:                 continue;
 7889:             }
 7890:             $encoding = substr($part, 0, 2);
 7891:             $text     = substr($part, 2);
 7892:             if (!ctype_xdigit($encoding)) {
 7893:                 $ret .= '%25' . $part;
 7894:                 continue;
 7895:             }
 7896:             $int = hexdec($encoding);
 7897:             if (isset($this->preserve[$int])) {
 7898:                 $ret .= chr($int) . $text;
 7899:                 continue;
 7900:             }
 7901:             $encoding = strtoupper($encoding);
 7902:             $ret .= '%' . $encoding . $text;
 7903:         }
 7904:         return $ret;
 7905:     }
 7906: }
 7907: 
 7908: 
 7909: 
 7910: 
 7911: 
 7912:  7913:  7914: 
 7915: class HTMLPurifier_PropertyList
 7916: {
 7917:      7918:  7919:  7920: 
 7921:     protected $data = array();
 7922: 
 7923:      7924:  7925:  7926: 
 7927:     protected $parent;
 7928: 
 7929:      7930:  7931:  7932: 
 7933:     protected $cache;
 7934: 
 7935:      7936:  7937: 
 7938:     public function __construct($parent = null)
 7939:     {
 7940:         $this->parent = $parent;
 7941:     }
 7942: 
 7943:      7944:  7945:  7946:  7947: 
 7948:     public function get($name)
 7949:     {
 7950:         if ($this->has($name)) {
 7951:             return $this->data[$name];
 7952:         }
 7953:         
 7954:         if ($this->parent) {
 7955:             return $this->parent->get($name);
 7956:         }
 7957:         throw new HTMLPurifier_Exception("Key '$name' not found");
 7958:     }
 7959: 
 7960:      7961:  7962:  7963:  7964: 
 7965:     public function set($name, $value)
 7966:     {
 7967:         $this->data[$name] = $value;
 7968:     }
 7969: 
 7970:      7971:  7972:  7973:  7974: 
 7975:     public function has($name)
 7976:     {
 7977:         return array_key_exists($name, $this->data);
 7978:     }
 7979: 
 7980:      7981:  7982:  7983:  7984: 
 7985:     public function reset($name = null)
 7986:     {
 7987:         if ($name == null) {
 7988:             $this->data = array();
 7989:         } else {
 7990:             unset($this->data[$name]);
 7991:         }
 7992:     }
 7993: 
 7994:      7995:  7996:  7997:  7998:  7999: 
 8000:     public function squash($force = false)
 8001:     {
 8002:         if ($this->cache !== null && !$force) {
 8003:             return $this->cache;
 8004:         }
 8005:         if ($this->parent) {
 8006:             return $this->cache = array_merge($this->parent->squash($force), $this->data);
 8007:         } else {
 8008:             return $this->cache = $this->data;
 8009:         }
 8010:     }
 8011: 
 8012:      8013:  8014:  8015: 
 8016:     public function getParent()
 8017:     {
 8018:         return $this->parent;
 8019:     }
 8020: 
 8021:      8022:  8023:  8024: 
 8025:     public function setParent($plist)
 8026:     {
 8027:         $this->parent = $plist;
 8028:     }
 8029: }
 8030: 
 8031: 
 8032: 
 8033: 
 8034: 
 8035:  8036:  8037: 
 8038: class HTMLPurifier_PropertyListIterator extends FilterIterator
 8039: {
 8040: 
 8041:      8042:  8043: 
 8044:     protected $l;
 8045:      8046:  8047: 
 8048:     protected $filter;
 8049: 
 8050:      8051:  8052:  8053: 
 8054:     public function __construct(Iterator $iterator, $filter = null)
 8055:     {
 8056:         parent::__construct($iterator);
 8057:         $this->l = strlen($filter);
 8058:         $this->filter = $filter;
 8059:     }
 8060: 
 8061:      8062:  8063: 
 8064:     public function accept()
 8065:     {
 8066:         $key = $this->getInnerIterator()->key();
 8067:         if (strncmp($key, $this->filter, $this->l) !== 0) {
 8068:             return false;
 8069:         }
 8070:         return true;
 8071:     }
 8072: }
 8073: 
 8074: 
 8075: 
 8076: 
 8077: 
 8078:  8079:  8080:  8081:  8082:  8083:  8084:  8085:  8086:  8087:  8088:  8089:  8090:  8091:  8092:  8093:  8094: 
 8095: class HTMLPurifier_Queue {
 8096:     private $input;
 8097:     private $output;
 8098: 
 8099:     public function __construct($input = array()) {
 8100:         $this->input = $input;
 8101:         $this->output = array();
 8102:     }
 8103: 
 8104:      8105:  8106: 
 8107:     public function shift() {
 8108:         if (empty($this->output)) {
 8109:             $this->output = array_reverse($this->input);
 8110:             $this->input = array();
 8111:         }
 8112:         if (empty($this->output)) {
 8113:             return NULL;
 8114:         }
 8115:         return array_pop($this->output);
 8116:     }
 8117: 
 8118:      8119:  8120: 
 8121:     public function push($x) {
 8122:         array_push($this->input, $x);
 8123:     }
 8124: 
 8125:      8126:  8127: 
 8128:     public function isEmpty() {
 8129:         return empty($this->input) && empty($this->output);
 8130:     }
 8131: }
 8132: 
 8133: 
 8134: 
 8135:  8136:  8137:  8138:  8139:  8140:  8141: 
 8142: 
 8143: 
 8144: abstract class HTMLPurifier_Strategy
 8145: {
 8146: 
 8147:      8148:  8149:  8150:  8151:  8152:  8153:  8154: 
 8155:     abstract public function execute($tokens, $config, $context);
 8156: }
 8157: 
 8158: 
 8159: 
 8160: 
 8161: 
 8162:  8163:  8164:  8165:  8166:  8167:  8168:  8169: 
 8170: class HTMLPurifier_StringHash extends ArrayObject
 8171: {
 8172:      8173:  8174: 
 8175:     protected $accessed = array();
 8176: 
 8177:      8178:  8179:  8180:  8181: 
 8182:     public function offsetGet($index)
 8183:     {
 8184:         $this->accessed[$index] = true;
 8185:         return parent::offsetGet($index);
 8186:     }
 8187: 
 8188:      8189:  8190:  8191: 
 8192:     public function getAccessed()
 8193:     {
 8194:         return $this->accessed;
 8195:     }
 8196: 
 8197:      8198:  8199: 
 8200:     public function resetAccessed()
 8201:     {
 8202:         $this->accessed = array();
 8203:     }
 8204: }
 8205: 
 8206: 
 8207: 
 8208: 
 8209: 
 8210:  8211:  8212:  8213:  8214:  8215:  8216:  8217:  8218:  8219:  8220:  8221:  8222:  8223:  8224:  8225:  8226:  8227:  8228:  8229:  8230:  8231:  8232:  8233:  8234: 
 8235: class HTMLPurifier_StringHashParser
 8236: {
 8237: 
 8238:      8239:  8240: 
 8241:     public $default = 'ID';
 8242: 
 8243:      8244:  8245:  8246:  8247: 
 8248:     public function parseFile($file)
 8249:     {
 8250:         if (!file_exists($file)) {
 8251:             return false;
 8252:         }
 8253:         $fh = fopen($file, 'r');
 8254:         if (!$fh) {
 8255:             return false;
 8256:         }
 8257:         $ret = $this->parseHandle($fh);
 8258:         fclose($fh);
 8259:         return $ret;
 8260:     }
 8261: 
 8262:      8263:  8264:  8265:  8266: 
 8267:     public function parseMultiFile($file)
 8268:     {
 8269:         if (!file_exists($file)) {
 8270:             return false;
 8271:         }
 8272:         $ret = array();
 8273:         $fh = fopen($file, 'r');
 8274:         if (!$fh) {
 8275:             return false;
 8276:         }
 8277:         while (!feof($fh)) {
 8278:             $ret[] = $this->parseHandle($fh);
 8279:         }
 8280:         fclose($fh);
 8281:         return $ret;
 8282:     }
 8283: 
 8284:      8285:  8286:  8287:  8288:  8289:  8290:  8291:  8292: 
 8293:     protected function parseHandle($fh)
 8294:     {
 8295:         $state   = false;
 8296:         $single  = false;
 8297:         $ret     = array();
 8298:         do {
 8299:             $line = fgets($fh);
 8300:             if ($line === false) {
 8301:                 break;
 8302:             }
 8303:             $line = rtrim($line, "\n\r");
 8304:             if (!$state && $line === '') {
 8305:                 continue;
 8306:             }
 8307:             if ($line === '----') {
 8308:                 break;
 8309:             }
 8310:             if (strncmp('--#', $line, 3) === 0) {
 8311:                 
 8312:                 continue;
 8313:             } elseif (strncmp('--', $line, 2) === 0) {
 8314:                 
 8315:                 $state = trim($line, '- ');
 8316:                 if (!isset($ret[$state])) {
 8317:                     $ret[$state] = '';
 8318:                 }
 8319:                 continue;
 8320:             } elseif (!$state) {
 8321:                 $single = true;
 8322:                 if (strpos($line, ':') !== false) {
 8323:                     
 8324:                     list($state, $line) = explode(':', $line, 2);
 8325:                     $line = trim($line);
 8326:                 } else {
 8327:                     
 8328:                     $state  = $this->default;
 8329:                 }
 8330:             }
 8331:             if ($single) {
 8332:                 $ret[$state] = $line;
 8333:                 $single = false;
 8334:                 $state  = false;
 8335:             } else {
 8336:                 $ret[$state] .= "$line\n";
 8337:             }
 8338:         } while (!feof($fh));
 8339:         return $ret;
 8340:     }
 8341: }
 8342: 
 8343: 
 8344: 
 8345: 
 8346: 
 8347:  8348:  8349: 
 8350: abstract class HTMLPurifier_TagTransform
 8351: {
 8352: 
 8353:      8354:  8355:  8356: 
 8357:     public $transform_to;
 8358: 
 8359:      8360:  8361:  8362:  8363:  8364: 
 8365:     abstract public function transform($tag, $config, $context);
 8366: 
 8367:      8368:  8369:  8370:  8371:  8372:  8373: 
 8374:     protected function prependCSS(&$attr, $css)
 8375:     {
 8376:         $attr['style'] = isset($attr['style']) ? $attr['style'] : '';
 8377:         $attr['style'] = $css . $attr['style'];
 8378:     }
 8379: }
 8380: 
 8381: 
 8382: 
 8383: 
 8384: 
 8385:  8386:  8387: 
 8388: abstract class HTMLPurifier_Token
 8389: {
 8390:      8391:  8392:  8393: 
 8394:     public $line;
 8395: 
 8396:      8397:  8398:  8399: 
 8400:     public $col;
 8401: 
 8402:      8403:  8404:  8405:  8406:  8407: 
 8408:     public $armor = array();
 8409: 
 8410:      8411:  8412:  8413: 
 8414:     public $skip;
 8415: 
 8416:      8417:  8418: 
 8419:     public $rewind;
 8420: 
 8421:      8422:  8423: 
 8424:     public $carryover;
 8425: 
 8426:      8427:  8428:  8429: 
 8430:     public function __get($n)
 8431:     {
 8432:         if ($n === 'type') {
 8433:             trigger_error('Deprecated type property called; use instanceof', E_USER_NOTICE);
 8434:             switch (get_class($this)) {
 8435:                 case 'HTMLPurifier_Token_Start':
 8436:                     return 'start';
 8437:                 case 'HTMLPurifier_Token_Empty':
 8438:                     return 'empty';
 8439:                 case 'HTMLPurifier_Token_End':
 8440:                     return 'end';
 8441:                 case 'HTMLPurifier_Token_Text':
 8442:                     return 'text';
 8443:                 case 'HTMLPurifier_Token_Comment':
 8444:                     return 'comment';
 8445:                 default:
 8446:                     return null;
 8447:             }
 8448:         }
 8449:     }
 8450: 
 8451:      8452:  8453:  8454:  8455: 
 8456:     public function position($l = null, $c = null)
 8457:     {
 8458:         $this->line = $l;
 8459:         $this->col = $c;
 8460:     }
 8461: 
 8462:      8463:  8464:  8465:  8466: 
 8467:     public function rawPosition($l, $c)
 8468:     {
 8469:         if ($c === -1) {
 8470:             $l++;
 8471:         }
 8472:         $this->line = $l;
 8473:         $this->col = $c;
 8474:     }
 8475: 
 8476:      8477:  8478: 
 8479:     abstract public function toNode();
 8480: }
 8481: 
 8482: 
 8483: 
 8484: 
 8485: 
 8486:  8487:  8488:  8489:  8490:  8491:  8492:  8493:  8494:  8495:  8496: 
 8497: class HTMLPurifier_TokenFactory
 8498: {
 8499:     
 8500: 
 8501:      8502:  8503: 
 8504:     private $p_start;
 8505: 
 8506:      8507:  8508: 
 8509:     private $p_end;
 8510: 
 8511:      8512:  8513: 
 8514:     private $p_empty;
 8515: 
 8516:      8517:  8518: 
 8519:     private $p_text;
 8520: 
 8521:      8522:  8523: 
 8524:     private ;
 8525: 
 8526:      8527:  8528: 
 8529:     public function __construct()
 8530:     {
 8531:         $this->p_start = new HTMLPurifier_Token_Start('', array());
 8532:         $this->p_end = new HTMLPurifier_Token_End('');
 8533:         $this->p_empty = new HTMLPurifier_Token_Empty('', array());
 8534:         $this->p_text = new HTMLPurifier_Token_Text('');
 8535:         $this->p_comment = new HTMLPurifier_Token_Comment('');
 8536:     }
 8537: 
 8538:      8539:  8540:  8541:  8542:  8543: 
 8544:     public function createStart($name, $attr = array())
 8545:     {
 8546:         $p = clone $this->p_start;
 8547:         $p->__construct($name, $attr);
 8548:         return $p;
 8549:     }
 8550: 
 8551:      8552:  8553:  8554:  8555: 
 8556:     public function createEnd($name)
 8557:     {
 8558:         $p = clone $this->p_end;
 8559:         $p->__construct($name);
 8560:         return $p;
 8561:     }
 8562: 
 8563:      8564:  8565:  8566:  8567:  8568: 
 8569:     public function createEmpty($name, $attr = array())
 8570:     {
 8571:         $p = clone $this->p_empty;
 8572:         $p->__construct($name, $attr);
 8573:         return $p;
 8574:     }
 8575: 
 8576:      8577:  8578:  8579:  8580: 
 8581:     public function createText($data)
 8582:     {
 8583:         $p = clone $this->p_text;
 8584:         $p->__construct($data);
 8585:         return $p;
 8586:     }
 8587: 
 8588:      8589:  8590:  8591:  8592: 
 8593:     public function ($data)
 8594:     {
 8595:         $p = clone $this->p_comment;
 8596:         $p->__construct($data);
 8597:         return $p;
 8598:     }
 8599: }
 8600: 
 8601: 
 8602: 
 8603: 
 8604: 
 8605:  8606:  8607:  8608:  8609:  8610:  8611:  8612: 
 8613: class HTMLPurifier_URI
 8614: {
 8615:      8616:  8617: 
 8618:     public $scheme;
 8619: 
 8620:      8621:  8622: 
 8623:     public $userinfo;
 8624: 
 8625:      8626:  8627: 
 8628:     public $host;
 8629: 
 8630:      8631:  8632: 
 8633:     public $port;
 8634: 
 8635:      8636:  8637: 
 8638:     public $path;
 8639: 
 8640:      8641:  8642: 
 8643:     public $query;
 8644: 
 8645:      8646:  8647: 
 8648:     public $fragment;
 8649: 
 8650:      8651:  8652:  8653:  8654:  8655:  8656:  8657:  8658:  8659: 
 8660:     public function __construct($scheme, $userinfo, $host, $port, $path, $query, $fragment)
 8661:     {
 8662:         $this->scheme = is_null($scheme) || ctype_lower($scheme) ? $scheme : strtolower($scheme);
 8663:         $this->userinfo = $userinfo;
 8664:         $this->host = $host;
 8665:         $this->port = is_null($port) ? $port : (int)$port;
 8666:         $this->path = $path;
 8667:         $this->query = $query;
 8668:         $this->fragment = $fragment;
 8669:     }
 8670: 
 8671:      8672:  8673:  8674:  8675:  8676: 
 8677:     public function getSchemeObj($config, $context)
 8678:     {
 8679:         $registry = HTMLPurifier_URISchemeRegistry::instance();
 8680:         if ($this->scheme !== null) {
 8681:             $scheme_obj = $registry->getScheme($this->scheme, $config, $context);
 8682:             if (!$scheme_obj) {
 8683:                 return false;
 8684:             } 
 8685:         } else {
 8686:             
 8687:             $def = $config->getDefinition('URI');
 8688:             $scheme_obj = $def->getDefaultScheme($config, $context);
 8689:             if (!$scheme_obj) {
 8690:                 
 8691:                 trigger_error(
 8692:                     'Default scheme object "' . $def->defaultScheme . '" was not readable',
 8693:                     E_USER_WARNING
 8694:                 );
 8695:                 return false;
 8696:             }
 8697:         }
 8698:         return $scheme_obj;
 8699:     }
 8700: 
 8701:      8702:  8703:  8704:  8705:  8706:  8707: 
 8708:     public function validate($config, $context)
 8709:     {
 8710:         
 8711:         $chars_sub_delims = '!$&\'()*+,;=';
 8712:         $chars_gen_delims = ':/?#[]@';
 8713:         $chars_pchar = $chars_sub_delims . ':@';
 8714: 
 8715:         
 8716:         if (!is_null($this->host)) {
 8717:             $host_def = new HTMLPurifier_AttrDef_URI_Host();
 8718:             $this->host = $host_def->validate($this->host, $config, $context);
 8719:             if ($this->host === false) {
 8720:                 $this->host = null;
 8721:             }
 8722:         }
 8723: 
 8724:         
 8725:         
 8726:         
 8727:         
 8728:         
 8729:         
 8730:         if (!is_null($this->scheme) && is_null($this->host) || $this->host === '') {
 8731:             
 8732:             
 8733:             $def = $config->getDefinition('URI');
 8734:             if ($def->defaultScheme === $this->scheme) {
 8735:                 $this->scheme = null;
 8736:             }
 8737:         }
 8738: 
 8739:         
 8740:         if (!is_null($this->userinfo)) {
 8741:             $encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . ':');
 8742:             $this->userinfo = $encoder->encode($this->userinfo);
 8743:         }
 8744: 
 8745:         
 8746:         if (!is_null($this->port)) {
 8747:             if ($this->port < 1 || $this->port > 65535) {
 8748:                 $this->port = null;
 8749:             }
 8750:         }
 8751: 
 8752:         
 8753:         $segments_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/');
 8754:         if (!is_null($this->host)) { 
 8755:             
 8756:             
 8757:             
 8758:             
 8759:             
 8760:             
 8761:             
 8762:             
 8763:             $this->path = $segments_encoder->encode($this->path);
 8764:         } elseif ($this->path !== '') {
 8765:             if ($this->path[0] === '/') {
 8766:                 
 8767:                 
 8768:                 
 8769:                 if (strlen($this->path) >= 2 && $this->path[1] === '/') {
 8770:                     
 8771:                     
 8772:                     
 8773:                     
 8774:                     $this->path = '';
 8775:                 } else {
 8776:                     $this->path = $segments_encoder->encode($this->path);
 8777:                 }
 8778:             } elseif (!is_null($this->scheme)) {
 8779:                 
 8780:                 
 8781:                 
 8782:                 $this->path = $segments_encoder->encode($this->path);
 8783:             } else {
 8784:                 
 8785:                 
 8786:                 
 8787:                 $segment_nc_encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . '@');
 8788:                 $c = strpos($this->path, '/');
 8789:                 if ($c !== false) {
 8790:                     $this->path =
 8791:                         $segment_nc_encoder->encode(substr($this->path, 0, $c)) .
 8792:                         $segments_encoder->encode(substr($this->path, $c));
 8793:                 } else {
 8794:                     $this->path = $segment_nc_encoder->encode($this->path);
 8795:                 }
 8796:             }
 8797:         } else {
 8798:             
 8799:             $this->path = ''; 
 8800:         }
 8801: 
 8802:         
 8803:         $qf_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/?');
 8804: 
 8805:         if (!is_null($this->query)) {
 8806:             $this->query = $qf_encoder->encode($this->query);
 8807:         }
 8808: 
 8809:         if (!is_null($this->fragment)) {
 8810:             $this->fragment = $qf_encoder->encode($this->fragment);
 8811:         }
 8812:         return true;
 8813:     }
 8814: 
 8815:      8816:  8817:  8818: 
 8819:     public function toString()
 8820:     {
 8821:         
 8822:         $authority = null;
 8823:         
 8824:         
 8825:         
 8826:         if (!is_null($this->host)) {
 8827:             $authority = '';
 8828:             if (!is_null($this->userinfo)) {
 8829:                 $authority .= $this->userinfo . '@';
 8830:             }
 8831:             $authority .= $this->host;
 8832:             if (!is_null($this->port)) {
 8833:                 $authority .= ':' . $this->port;
 8834:             }
 8835:         }
 8836: 
 8837:         
 8838:         
 8839:         
 8840:         
 8841:         
 8842:         
 8843:         $result = '';
 8844:         if (!is_null($this->scheme)) {
 8845:             $result .= $this->scheme . ':';
 8846:         }
 8847:         if (!is_null($authority)) {
 8848:             $result .= '//' . $authority;
 8849:         }
 8850:         $result .= $this->path;
 8851:         if (!is_null($this->query)) {
 8852:             $result .= '?' . $this->query;
 8853:         }
 8854:         if (!is_null($this->fragment)) {
 8855:             $result .= '#' . $this->fragment;
 8856:         }
 8857: 
 8858:         return $result;
 8859:     }
 8860: 
 8861:      8862:  8863:  8864:  8865:  8866:  8867:  8868:  8869:  8870:  8871:  8872: 
 8873:     public function isLocal($config, $context)
 8874:     {
 8875:         if ($this->host === null) {
 8876:             return true;
 8877:         }
 8878:         $uri_def = $config->getDefinition('URI');
 8879:         if ($uri_def->host === $this->host) {
 8880:             return true;
 8881:         }
 8882:         return false;
 8883:     }
 8884: 
 8885:      8886:  8887:  8888:  8889:  8890:  8891:  8892:  8893:  8894: 
 8895:     public function isBenign($config, $context)
 8896:     {
 8897:         if (!$this->isLocal($config, $context)) {
 8898:             return false;
 8899:         }
 8900: 
 8901:         $scheme_obj = $this->getSchemeObj($config, $context);
 8902:         if (!$scheme_obj) {
 8903:             return false;
 8904:         } 
 8905: 
 8906:         $current_scheme_obj = $config->getDefinition('URI')->getDefaultScheme($config, $context);
 8907:         if ($current_scheme_obj->secure) {
 8908:             if (!$scheme_obj->secure) {
 8909:                 return false;
 8910:             }
 8911:         }
 8912:         return true;
 8913:     }
 8914: }
 8915: 
 8916: 
 8917: 
 8918: 
 8919: 
 8920: class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition
 8921: {
 8922: 
 8923:     public $type = 'URI';
 8924:     protected $filters = array();
 8925:     protected $postFilters = array();
 8926:     protected $registeredFilters = array();
 8927: 
 8928:      8929:  8930: 
 8931:     public $base;
 8932: 
 8933:      8934:  8935: 
 8936:     public $host;
 8937: 
 8938:      8939:  8940: 
 8941:     public $defaultScheme;
 8942: 
 8943:     public function __construct()
 8944:     {
 8945:         $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternal());
 8946:         $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternalResources());
 8947:         $this->registerFilter(new HTMLPurifier_URIFilter_DisableResources());
 8948:         $this->registerFilter(new HTMLPurifier_URIFilter_HostBlacklist());
 8949:         $this->registerFilter(new HTMLPurifier_URIFilter_SafeIframe());
 8950:         $this->registerFilter(new HTMLPurifier_URIFilter_MakeAbsolute());
 8951:         $this->registerFilter(new HTMLPurifier_URIFilter_Munge());
 8952:     }
 8953: 
 8954:     public function registerFilter($filter)
 8955:     {
 8956:         $this->registeredFilters[$filter->name] = $filter;
 8957:     }
 8958: 
 8959:     public function addFilter($filter, $config)
 8960:     {
 8961:         $r = $filter->prepare($config);
 8962:         if ($r === false) return; 
 8963:         if ($filter->post) {
 8964:             $this->postFilters[$filter->name] = $filter;
 8965:         } else {
 8966:             $this->filters[$filter->name] = $filter;
 8967:         }
 8968:     }
 8969: 
 8970:     protected function doSetup($config)
 8971:     {
 8972:         $this->setupMemberVariables($config);
 8973:         $this->setupFilters($config);
 8974:     }
 8975: 
 8976:     protected function setupFilters($config)
 8977:     {
 8978:         foreach ($this->registeredFilters as $name => $filter) {
 8979:             if ($filter->always_load) {
 8980:                 $this->addFilter($filter, $config);
 8981:             } else {
 8982:                 $conf = $config->get('URI.' . $name);
 8983:                 if ($conf !== false && $conf !== null) {
 8984:                     $this->addFilter($filter, $config);
 8985:                 }
 8986:             }
 8987:         }
 8988:         unset($this->registeredFilters);
 8989:     }
 8990: 
 8991:     protected function setupMemberVariables($config)
 8992:     {
 8993:         $this->host = $config->get('URI.Host');
 8994:         $base_uri = $config->get('URI.Base');
 8995:         if (!is_null($base_uri)) {
 8996:             $parser = new HTMLPurifier_URIParser();
 8997:             $this->base = $parser->parse($base_uri);
 8998:             $this->defaultScheme = $this->base->scheme;
 8999:             if (is_null($this->host)) $this->host = $this->base->host;
 9000:         }
 9001:         if (is_null($this->defaultScheme)) $this->defaultScheme = $config->get('URI.DefaultScheme');
 9002:     }
 9003: 
 9004:     public function getDefaultScheme($config, $context)
 9005:     {
 9006:         return HTMLPurifier_URISchemeRegistry::instance()->getScheme($this->defaultScheme, $config, $context);
 9007:     }
 9008: 
 9009:     public function filter(&$uri, $config, $context)
 9010:     {
 9011:         foreach ($this->filters as $name => $f) {
 9012:             $result = $f->filter($uri, $config, $context);
 9013:             if (!$result) return false;
 9014:         }
 9015:         return true;
 9016:     }
 9017: 
 9018:     public function postFilter(&$uri, $config, $context)
 9019:     {
 9020:         foreach ($this->postFilters as $name => $f) {
 9021:             $result = $f->filter($uri, $config, $context);
 9022:             if (!$result) return false;
 9023:         }
 9024:         return true;
 9025:     }
 9026: 
 9027: }
 9028: 
 9029: 
 9030: 
 9031: 
 9032: 
 9033:  9034:  9035:  9036:  9037:  9038:  9039:  9040:  9041:  9042:  9043:  9044:  9045:  9046:  9047:  9048:  9049:  9050:  9051:  9052:  9053:  9054:  9055:  9056:  9057: 
 9058: abstract class HTMLPurifier_URIFilter
 9059: {
 9060: 
 9061:      9062:  9063:  9064: 
 9065:     public $name;
 9066: 
 9067:      9068:  9069:  9070: 
 9071:     public $post = false;
 9072: 
 9073:      9074:  9075:  9076:  9077:  9078: 
 9079:     public $always_load = false;
 9080: 
 9081:      9082:  9083:  9084:  9085:  9086: 
 9087:     public function prepare($config)
 9088:     {
 9089:         return true;
 9090:     }
 9091: 
 9092:      9093:  9094:  9095:  9096:  9097:  9098:  9099:  9100: 
 9101:     abstract public function filter(&$uri, $config, $context);
 9102: }
 9103: 
 9104: 
 9105: 
 9106: 
 9107: 
 9108:  9109:  9110:  9111: 
 9112: class HTMLPurifier_URIParser
 9113: {
 9114: 
 9115:      9116:  9117: 
 9118:     protected $percentEncoder;
 9119: 
 9120:     public function __construct()
 9121:     {
 9122:         $this->percentEncoder = new HTMLPurifier_PercentEncoder();
 9123:     }
 9124: 
 9125:      9126:  9127:  9128:  9129:  9130: 
 9131:     public function parse($uri)
 9132:     {
 9133:         $uri = $this->percentEncoder->normalize($uri);
 9134: 
 9135:         
 9136:         
 9137:         
 9138:         $r_URI = '!'.
 9139:             '(([a-zA-Z0-9\.\+\-]+):)?'. 
 9140:             '(//([^/?#"<>]*))?'. 
 9141:             '([^?#"<>]*)'.       
 9142:             '(\?([^#"<>]*))?'.   
 9143:             '(#([^"<>]*))?'.     
 9144:             '!';
 9145: 
 9146:         $matches = array();
 9147:         $result = preg_match($r_URI, $uri, $matches);
 9148: 
 9149:         if (!$result) return false; 
 9150: 
 9151:         
 9152:         $scheme     = !empty($matches[1]) ? $matches[2] : null;
 9153:         $authority  = !empty($matches[3]) ? $matches[4] : null;
 9154:         $path       = $matches[5]; 
 9155:         $query      = !empty($matches[6]) ? $matches[7] : null;
 9156:         $fragment   = !empty($matches[8]) ? $matches[9] : null;
 9157: 
 9158:         
 9159:         if ($authority !== null) {
 9160:             $r_authority = "/^((.+?)@)?(\[[^\]]+\]|[^:]*)(:(\d*))?/";
 9161:             $matches = array();
 9162:             preg_match($r_authority, $authority, $matches);
 9163:             $userinfo   = !empty($matches[1]) ? $matches[2] : null;
 9164:             $host       = !empty($matches[3]) ? $matches[3] : '';
 9165:             $port       = !empty($matches[4]) ? (int) $matches[5] : null;
 9166:         } else {
 9167:             $port = $host = $userinfo = null;
 9168:         }
 9169: 
 9170:         return new HTMLPurifier_URI(
 9171:             $scheme, $userinfo, $host, $port, $path, $query, $fragment);
 9172:     }
 9173: 
 9174: }
 9175: 
 9176: 
 9177: 
 9178: 
 9179: 
 9180:  9181:  9182: 
 9183: abstract class HTMLPurifier_URIScheme
 9184: {
 9185: 
 9186:      9187:  9188:  9189:  9190:  9191: 
 9192:     public $default_port = null;
 9193: 
 9194:      9195:  9196:  9197:  9198: 
 9199:     public $browsable = false;
 9200: 
 9201:      9202:  9203:  9204:  9205: 
 9206:     public $secure = false;
 9207: 
 9208:      9209:  9210:  9211:  9212: 
 9213:     public $hierarchical = false;
 9214: 
 9215:      9216:  9217:  9218:  9219:  9220: 
 9221:     public $may_omit_host = false;
 9222: 
 9223:      9224:  9225:  9226:  9227:  9228:  9229: 
 9230:     abstract public function doValidate(&$uri, $config, $context);
 9231: 
 9232:      9233:  9234:  9235:  9236:  9237:  9238:  9239: 
 9240:     public function validate(&$uri, $config, $context)
 9241:     {
 9242:         if ($this->default_port == $uri->port) {
 9243:             $uri->port = null;
 9244:         }
 9245:         
 9246:         
 9247:         if (!$this->may_omit_host &&
 9248:             
 9249:             (!is_null($uri->scheme) && ($uri->host === '' || is_null($uri->host))) ||
 9250:             
 9251:             
 9252:             
 9253:             (is_null($uri->scheme) && $uri->host === '')
 9254:         ) {
 9255:             do {
 9256:                 if (is_null($uri->scheme)) {
 9257:                     if (substr($uri->path, 0, 2) != '//') {
 9258:                         $uri->host = null;
 9259:                         break;
 9260:                     }
 9261:                     
 9262:                     
 9263:                     
 9264:                 }
 9265:                 
 9266:                 $host = $config->get('URI.Host');
 9267:                 if (!is_null($host)) {
 9268:                     $uri->host = $host;
 9269:                 } else {
 9270:                     
 9271:                     return false;
 9272:                 }
 9273:             } while (false);
 9274:         }
 9275:         return $this->doValidate($uri, $config, $context);
 9276:     }
 9277: }
 9278: 
 9279: 
 9280: 
 9281: 
 9282: 
 9283:  9284:  9285: 
 9286: class HTMLPurifier_URISchemeRegistry
 9287: {
 9288: 
 9289:      9290:  9291:  9292:  9293:  9294:  9295:  9296: 
 9297:     public static function instance($prototype = null)
 9298:     {
 9299:         static $instance = null;
 9300:         if ($prototype !== null) {
 9301:             $instance = $prototype;
 9302:         } elseif ($instance === null || $prototype == true) {
 9303:             $instance = new HTMLPurifier_URISchemeRegistry();
 9304:         }
 9305:         return $instance;
 9306:     }
 9307: 
 9308:      9309:  9310:  9311: 
 9312:     protected $schemes = array();
 9313: 
 9314:      9315:  9316:  9317:  9318:  9319:  9320: 
 9321:     public function getScheme($scheme, $config, $context)
 9322:     {
 9323:         if (!$config) {
 9324:             $config = HTMLPurifier_Config::createDefault();
 9325:         }
 9326: 
 9327:         
 9328:         $allowed_schemes = $config->get('URI.AllowedSchemes');
 9329:         if (!$config->get('URI.OverrideAllowedSchemes') &&
 9330:             !isset($allowed_schemes[$scheme])
 9331:         ) {
 9332:             return;
 9333:         }
 9334: 
 9335:         if (isset($this->schemes[$scheme])) {
 9336:             return $this->schemes[$scheme];
 9337:         }
 9338:         if (!isset($allowed_schemes[$scheme])) {
 9339:             return;
 9340:         }
 9341: 
 9342:         $class = 'HTMLPurifier_URIScheme_' . $scheme;
 9343:         if (!class_exists($class)) {
 9344:             return;
 9345:         }
 9346:         $this->schemes[$scheme] = new $class();
 9347:         return $this->schemes[$scheme];
 9348:     }
 9349: 
 9350:      9351:  9352:  9353:  9354: 
 9355:     public function register($scheme, $scheme_obj)
 9356:     {
 9357:         $this->schemes[$scheme] = $scheme_obj;
 9358:     }
 9359: }
 9360: 
 9361: 
 9362: 
 9363: 
 9364: 
 9365:  9366:  9367:  9368: 
 9369: class HTMLPurifier_UnitConverter
 9370: {
 9371: 
 9372:     const ENGLISH = 1;
 9373:     const METRIC = 2;
 9374:     const DIGITAL = 3;
 9375: 
 9376:      9377:  9378:  9379:  9380:  9381:  9382:  9383:  9384: 
 9385:     protected static $units = array(
 9386:         self::ENGLISH => array(
 9387:             'px' => 3, 
 9388:             'pt' => 4,
 9389:             'pc' => 48,
 9390:             'in' => 288,
 9391:             self::METRIC => array('pt', '0.352777778', 'mm'),
 9392:         ),
 9393:         self::METRIC => array(
 9394:             'mm' => 1,
 9395:             'cm' => 10,
 9396:             self::ENGLISH => array('mm', '2.83464567', 'pt'),
 9397:         ),
 9398:     );
 9399: 
 9400:      9401:  9402:  9403: 
 9404:     protected $outputPrecision;
 9405: 
 9406:      9407:  9408:  9409: 
 9410:     protected $internalPrecision;
 9411: 
 9412:      9413:  9414:  9415: 
 9416:     private $bcmath;
 9417: 
 9418:     public function __construct($output_precision = 4, $internal_precision = 10, $force_no_bcmath = false)
 9419:     {
 9420:         $this->outputPrecision = $output_precision;
 9421:         $this->internalPrecision = $internal_precision;
 9422:         $this->bcmath = !$force_no_bcmath && function_exists('bcmul');
 9423:     }
 9424: 
 9425:      9426:  9427:  9428:  9429:  9430:  9431:  9432:  9433:  9434:  9435:  9436:  9437:  9438:  9439:  9440:  9441:  9442:  9443: 
 9444:     public function convert($length, $to_unit)
 9445:     {
 9446:         if (!$length->isValid()) {
 9447:             return false;
 9448:         }
 9449: 
 9450:         $n = $length->getN();
 9451:         $unit = $length->getUnit();
 9452: 
 9453:         if ($n === '0' || $unit === false) {
 9454:             return new HTMLPurifier_Length('0', false);
 9455:         }
 9456: 
 9457:         $state = $dest_state = false;
 9458:         foreach (self::$units as $k => $x) {
 9459:             if (isset($x[$unit])) {
 9460:                 $state = $k;
 9461:             }
 9462:             if (isset($x[$to_unit])) {
 9463:                 $dest_state = $k;
 9464:             }
 9465:         }
 9466:         if (!$state || !$dest_state) {
 9467:             return false;
 9468:         }
 9469: 
 9470:         
 9471:         
 9472:         $sigfigs = $this->getSigFigs($n);
 9473:         if ($sigfigs < $this->outputPrecision) {
 9474:             $sigfigs = $this->outputPrecision;
 9475:         }
 9476: 
 9477:         
 9478:         
 9479:         
 9480:         
 9481:         $log = (int)floor(log(abs($n), 10));
 9482:         $cp = ($log < 0) ? $this->internalPrecision - $log : $this->internalPrecision; 
 9483: 
 9484:         for ($i = 0; $i < 2; $i++) {
 9485: 
 9486:             
 9487:             if ($dest_state === $state) {
 9488:                 
 9489:                 $dest_unit = $to_unit;
 9490:             } else {
 9491:                 
 9492:                 $dest_unit = self::$units[$state][$dest_state][0];
 9493:             }
 9494: 
 9495:             
 9496:             if ($dest_unit !== $unit) {
 9497:                 $factor = $this->div(self::$units[$state][$unit], self::$units[$state][$dest_unit], $cp);
 9498:                 $n = $this->mul($n, $factor, $cp);
 9499:                 $unit = $dest_unit;
 9500:             }
 9501: 
 9502:             
 9503:             if ($n === '') {
 9504:                 $n = '0';
 9505:                 $unit = $to_unit;
 9506:                 break;
 9507:             }
 9508: 
 9509:             
 9510:             if ($dest_state === $state) {
 9511:                 break;
 9512:             }
 9513: 
 9514:             if ($i !== 0) {
 9515:                 
 9516:                 
 9517:                 return false;
 9518:             }
 9519: 
 9520:             
 9521: 
 9522:             
 9523:             $n = $this->mul($n, self::$units[$state][$dest_state][1], $cp);
 9524:             $unit = self::$units[$state][$dest_state][2];
 9525:             $state = $dest_state;
 9526: 
 9527:             
 9528: 
 9529:         }
 9530: 
 9531:         
 9532:         if ($unit !== $to_unit) {
 9533:             return false;
 9534:         }
 9535: 
 9536:         
 9537:         
 9538:         
 9539: 
 9540:         $n = $this->round($n, $sigfigs);
 9541:         if (strpos($n, '.') !== false) {
 9542:             $n = rtrim($n, '0');
 9543:         }
 9544:         $n = rtrim($n, '.');
 9545: 
 9546:         return new HTMLPurifier_Length($n, $unit);
 9547:     }
 9548: 
 9549:      9550:  9551:  9552:  9553: 
 9554:     public function getSigFigs($n)
 9555:     {
 9556:         $n = ltrim($n, '0+-');
 9557:         $dp = strpos($n, '.'); 
 9558:         if ($dp === false) {
 9559:             $sigfigs = strlen(rtrim($n, '0'));
 9560:         } else {
 9561:             $sigfigs = strlen(ltrim($n, '0.')); 
 9562:             if ($dp !== 0) {
 9563:                 $sigfigs--;
 9564:             }
 9565:         }
 9566:         return $sigfigs;
 9567:     }
 9568: 
 9569:      9570:  9571:  9572:  9573:  9574:  9575: 
 9576:     private function add($s1, $s2, $scale)
 9577:     {
 9578:         if ($this->bcmath) {
 9579:             return bcadd($s1, $s2, $scale);
 9580:         } else {
 9581:             return $this->scale((float)$s1 + (float)$s2, $scale);
 9582:         }
 9583:     }
 9584: 
 9585:      9586:  9587:  9588:  9589:  9590:  9591: 
 9592:     private function mul($s1, $s2, $scale)
 9593:     {
 9594:         if ($this->bcmath) {
 9595:             return bcmul($s1, $s2, $scale);
 9596:         } else {
 9597:             return $this->scale((float)$s1 * (float)$s2, $scale);
 9598:         }
 9599:     }
 9600: 
 9601:      9602:  9603:  9604:  9605:  9606:  9607: 
 9608:     private function div($s1, $s2, $scale)
 9609:     {
 9610:         if ($this->bcmath) {
 9611:             return bcdiv($s1, $s2, $scale);
 9612:         } else {
 9613:             return $this->scale((float)$s1 / (float)$s2, $scale);
 9614:         }
 9615:     }
 9616: 
 9617:      9618:  9619:  9620:  9621:  9622:  9623: 
 9624:     private function round($n, $sigfigs)
 9625:     {
 9626:         $new_log = (int)floor(log(abs($n), 10)); 
 9627:         $rp = $sigfigs - $new_log - 1; 
 9628:         $neg = $n < 0 ? '-' : ''; 
 9629:         if ($this->bcmath) {
 9630:             if ($rp >= 0) {
 9631:                 $n = bcadd($n, $neg . '0.' . str_repeat('0', $rp) . '5', $rp + 1);
 9632:                 $n = bcdiv($n, '1', $rp);
 9633:             } else {
 9634:                 
 9635:                 
 9636:                 $n = bcadd($n, $neg . '5' . str_repeat('0', $new_log - $sigfigs), 0);
 9637:                 $n = substr($n, 0, $sigfigs + strlen($neg)) . str_repeat('0', $new_log - $sigfigs + 1);
 9638:             }
 9639:             return $n;
 9640:         } else {
 9641:             return $this->scale(round($n, $sigfigs - $new_log - 1), $rp + 1);
 9642:         }
 9643:     }
 9644: 
 9645:      9646:  9647:  9648:  9649:  9650: 
 9651:     private function scale($r, $scale)
 9652:     {
 9653:         if ($scale < 0) {
 9654:             
 9655:             
 9656:             $r = sprintf('%.0f', (float)$r);
 9657:             
 9658:             
 9659:             
 9660:             
 9661:             $precise = (string)round(substr($r, 0, strlen($r) + $scale), -1);
 9662:             
 9663:             return substr($precise, 0, -1) . str_repeat('0', -$scale + 1);
 9664:         }
 9665:         return sprintf('%.' . $scale . 'f', (float)$r);
 9666:     }
 9667: }
 9668: 
 9669: 
 9670: 
 9671: 
 9672: 
 9673:  9674:  9675:  9676: 
 9677: class HTMLPurifier_VarParser
 9678: {
 9679: 
 9680:     const STRING = 1;
 9681:     const ISTRING = 2;
 9682:     const TEXT = 3;
 9683:     const ITEXT = 4;
 9684:     const INT = 5;
 9685:     const FLOAT = 6;
 9686:     const BOOL = 7;
 9687:     const LOOKUP = 8;
 9688:     const ALIST = 9;
 9689:     const HASH = 10;
 9690:     const MIXED = 11;
 9691: 
 9692:      9693:  9694:  9695: 
 9696:     public static $types = array(
 9697:         'string' => self::STRING,
 9698:         'istring' => self::ISTRING,
 9699:         'text' => self::TEXT,
 9700:         'itext' => self::ITEXT,
 9701:         'int' => self::INT,
 9702:         'float' => self::FLOAT,
 9703:         'bool' => self::BOOL,
 9704:         'lookup' => self::LOOKUP,
 9705:         'list' => self::ALIST,
 9706:         'hash' => self::HASH,
 9707:         'mixed' => self::MIXED
 9708:     );
 9709: 
 9710:      9711:  9712:  9713: 
 9714:     public static $stringTypes = array(
 9715:         self::STRING => true,
 9716:         self::ISTRING => true,
 9717:         self::TEXT => true,
 9718:         self::ITEXT => true,
 9719:     );
 9720: 
 9721:      9722:  9723:  9724:  9725:  9726:  9727:  9728:  9729:  9730: 
 9731:     final public function parse($var, $type, $allow_null = false)
 9732:     {
 9733:         if (is_string($type)) {
 9734:             if (!isset(HTMLPurifier_VarParser::$types[$type])) {
 9735:                 throw new HTMLPurifier_VarParserException("Invalid type '$type'");
 9736:             } else {
 9737:                 $type = HTMLPurifier_VarParser::$types[$type];
 9738:             }
 9739:         }
 9740:         $var = $this->parseImplementation($var, $type, $allow_null);
 9741:         if ($allow_null && $var === null) {
 9742:             return null;
 9743:         }
 9744:         
 9745:         
 9746:         switch ($type) {
 9747:             case (self::STRING):
 9748:             case (self::ISTRING):
 9749:             case (self::TEXT):
 9750:             case (self::ITEXT):
 9751:                 if (!is_string($var)) {
 9752:                     break;
 9753:                 }
 9754:                 if ($type == self::ISTRING || $type == self::ITEXT) {
 9755:                     $var = strtolower($var);
 9756:                 }
 9757:                 return $var;
 9758:             case (self::INT):
 9759:                 if (!is_int($var)) {
 9760:                     break;
 9761:                 }
 9762:                 return $var;
 9763:             case (self::FLOAT):
 9764:                 if (!is_float($var)) {
 9765:                     break;
 9766:                 }
 9767:                 return $var;
 9768:             case (self::BOOL):
 9769:                 if (!is_bool($var)) {
 9770:                     break;
 9771:                 }
 9772:                 return $var;
 9773:             case (self::LOOKUP):
 9774:             case (self::ALIST):
 9775:             case (self::HASH):
 9776:                 if (!is_array($var)) {
 9777:                     break;
 9778:                 }
 9779:                 if ($type === self::LOOKUP) {
 9780:                     foreach ($var as $k) {
 9781:                         if ($k !== true) {
 9782:                             $this->error('Lookup table contains value other than true');
 9783:                         }
 9784:                     }
 9785:                 } elseif ($type === self::ALIST) {
 9786:                     $keys = array_keys($var);
 9787:                     if (array_keys($keys) !== $keys) {
 9788:                         $this->error('Indices for list are not uniform');
 9789:                     }
 9790:                 }
 9791:                 return $var;
 9792:             case (self::MIXED):
 9793:                 return $var;
 9794:             default:
 9795:                 $this->errorInconsistent(get_class($this), $type);
 9796:         }
 9797:         $this->errorGeneric($var, $type);
 9798:     }
 9799: 
 9800:      9801:  9802:  9803:  9804:  9805:  9806:  9807: 
 9808:     protected function parseImplementation($var, $type, $allow_null)
 9809:     {
 9810:         return $var;
 9811:     }
 9812: 
 9813:      9814:  9815:  9816: 
 9817:     protected function error($msg)
 9818:     {
 9819:         throw new HTMLPurifier_VarParserException($msg);
 9820:     }
 9821: 
 9822:      9823:  9824:  9825:  9826:  9827:  9828:  9829:  9830: 
 9831:     protected function errorInconsistent($class, $type)
 9832:     {
 9833:         throw new HTMLPurifier_Exception(
 9834:             "Inconsistency in $class: " . HTMLPurifier_VarParser::getTypeName($type) .
 9835:             " not implemented"
 9836:         );
 9837:     }
 9838: 
 9839:      9840:  9841:  9842:  9843: 
 9844:     protected function errorGeneric($var, $type)
 9845:     {
 9846:         $vtype = gettype($var);
 9847:         $this->error("Expected type " . HTMLPurifier_VarParser::getTypeName($type) . ", got $vtype");
 9848:     }
 9849: 
 9850:      9851:  9852:  9853: 
 9854:     public static function getTypeName($type)
 9855:     {
 9856:         static $lookup;
 9857:         if (!$lookup) {
 9858:             
 9859:             $lookup = array_flip(HTMLPurifier_VarParser::$types);
 9860:         }
 9861:         if (!isset($lookup[$type])) {
 9862:             return 'unknown';
 9863:         }
 9864:         return $lookup[$type];
 9865:     }
 9866: }
 9867: 
 9868: 
 9869: 
 9870: 
 9871: 
 9872:  9873:  9874: 
 9875: class HTMLPurifier_VarParserException extends HTMLPurifier_Exception
 9876: {
 9877: 
 9878: }
 9879: 
 9880: 
 9881: 
 9882: 
 9883: 
 9884:  9885:  9886:  9887:  9888:  9889:  9890:  9891:  9892:  9893:  9894:  9895:  9896:  9897:  9898:  9899:  9900: 
 9901: 
 9902: class HTMLPurifier_Zipper
 9903: {
 9904:     public $front, $back;
 9905: 
 9906:     public function __construct($front, $back) {
 9907:         $this->front = $front;
 9908:         $this->back = $back;
 9909:     }
 9910: 
 9911:      9912:  9913:  9914:  9915:  9916: 
 9917:     static public function fromArray($array) {
 9918:         $z = new self(array(), array_reverse($array));
 9919:         $t = $z->delete(); 
 9920:         return array($z, $t);
 9921:     }
 9922: 
 9923:      9924:  9925:  9926:  9927: 
 9928:     public function toArray($t = NULL) {
 9929:         $a = $this->front;
 9930:         if ($t !== NULL) $a[] = $t;
 9931:         for ($i = count($this->back)-1; $i >= 0; $i--) {
 9932:             $a[] = $this->back[$i];
 9933:         }
 9934:         return $a;
 9935:     }
 9936: 
 9937:      9938:  9939:  9940:  9941: 
 9942:     public function next($t) {
 9943:         if ($t !== NULL) array_push($this->front, $t);
 9944:         return empty($this->back) ? NULL : array_pop($this->back);
 9945:     }
 9946: 
 9947:      9948:  9949:  9950:  9951:  9952: 
 9953:     public function advance($t, $n) {
 9954:         for ($i = 0; $i < $n; $i++) {
 9955:             $t = $this->next($t);
 9956:         }
 9957:         return $t;
 9958:     }
 9959: 
 9960:      9961:  9962:  9963:  9964: 
 9965:     public function prev($t) {
 9966:         if ($t !== NULL) array_push($this->back, $t);
 9967:         return empty($this->front) ? NULL : array_pop($this->front);
 9968:     }
 9969: 
 9970:      9971:  9972:  9973:  9974: 
 9975:     public function delete() {
 9976:         return empty($this->back) ? NULL : array_pop($this->back);
 9977:     }
 9978: 
 9979:      9980:  9981:  9982: 
 9983:     public function done() {
 9984:         return empty($this->back);
 9985:     }
 9986: 
 9987:      9988:  9989:  9990: 
 9991:     public function insertBefore($t) {
 9992:         if ($t !== NULL) array_push($this->front, $t);
 9993:     }
 9994: 
 9995:      9996:  9997:  9998: 
 9999:     public function insertAfter($t) {
10000:         if ($t !== NULL) array_push($this->back, $t);
10001:     }
10002: 
10003:     10004: 10005: 10006: 10007: 10008: 10009: 10010: 10011: 10012: 10013: 10014: 10015: 10016: 10017: 10018: 10019: 10020: 10021: 10022: 
10023:     public function splice($t, $delete, $replacement) {
10024:         
10025:         $old = array();
10026:         $r = $t;
10027:         for ($i = $delete; $i > 0; $i--) {
10028:             $old[] = $r;
10029:             $r = $this->delete();
10030:         }
10031:         
10032:         for ($i = count($replacement)-1; $i >= 0; $i--) {
10033:             $this->insertAfter($r);
10034:             $r = $replacement[$i];
10035:         }
10036:         return array($old, $r);
10037:     }
10038: }
10039: 
10040: 
10041: 
10042: 10043: 10044: 10045: 10046: 10047: 10048: 10049: 10050: 10051: 10052: 
10053: class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
10054: {
10055: 
10056:     10057: 10058: 10059: 10060: 10061: 
10062:     public function validate($css, $config, $context)
10063:     {
10064:         $css = $this->parseCDATA($css);
10065: 
10066:         $definition = $config->getCSSDefinition();
10067: 
10068:         
10069:         
10070:         
10071:         
10072:         
10073: 
10074:         $declarations = explode(';', $css);
10075:         $propvalues = array();
10076: 
10077:         10078: 10079: 
10080:         $property = false;
10081:         $context->register('CurrentCSSProperty', $property);
10082: 
10083:         foreach ($declarations as $declaration) {
10084:             if (!$declaration) {
10085:                 continue;
10086:             }
10087:             if (!strpos($declaration, ':')) {
10088:                 continue;
10089:             }
10090:             list($property, $value) = explode(':', $declaration, 2);
10091:             $property = trim($property);
10092:             $value = trim($value);
10093:             $ok = false;
10094:             do {
10095:                 if (isset($definition->info[$property])) {
10096:                     $ok = true;
10097:                     break;
10098:                 }
10099:                 if (ctype_lower($property)) {
10100:                     break;
10101:                 }
10102:                 $property = strtolower($property);
10103:                 if (isset($definition->info[$property])) {
10104:                     $ok = true;
10105:                     break;
10106:                 }
10107:             } while (0);
10108:             if (!$ok) {
10109:                 continue;
10110:             }
10111:             
10112:             if (strtolower(trim($value)) !== 'inherit') {
10113:                 
10114:                 $result = $definition->info[$property]->validate(
10115:                     $value,
10116:                     $config,
10117:                     $context
10118:                 );
10119:             } else {
10120:                 $result = 'inherit';
10121:             }
10122:             if ($result === false) {
10123:                 continue;
10124:             }
10125:             $propvalues[$property] = $result;
10126:         }
10127: 
10128:         $context->destroy('CurrentCSSProperty');
10129: 
10130:         
10131:         
10132:         
10133: 
10134:         $new_declarations = '';
10135:         foreach ($propvalues as $prop => $value) {
10136:             $new_declarations .= "$prop:$value;";
10137:         }
10138: 
10139:         return $new_declarations ? $new_declarations : false;
10140: 
10141:     }
10142: 
10143: }
10144: 
10145: 
10146: 
10147: 
10148: 
10149: 10150: 10151: 10152: 
10153: class HTMLPurifier_AttrDef_Clone extends HTMLPurifier_AttrDef
10154: {
10155:     10156: 10157: 10158: 
10159:     protected $clone;
10160: 
10161:     10162: 10163: 
10164:     public function __construct($clone)
10165:     {
10166:         $this->clone = $clone;
10167:     }
10168: 
10169:     10170: 10171: 10172: 10173: 10174: 
10175:     public function validate($v, $config, $context)
10176:     {
10177:         return $this->clone->validate($v, $config, $context);
10178:     }
10179: 
10180:     10181: 10182: 10183: 
10184:     public function make($string)
10185:     {
10186:         return clone $this->clone;
10187:     }
10188: }
10189: 
10190: 
10191: 
10192: 
10193: 
10194: 
10195: 10196: 10197: 10198: 10199: 10200: 
10201: class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef
10202: {
10203: 
10204:     10205: 10206: 10207: 10208: 
10209:     public $valid_values = array();
10210: 
10211:     10212: 10213: 10214: 
10215:     protected $case_sensitive = false; 
10216: 
10217:     10218: 10219: 10220: 
10221:     public function __construct($valid_values = array(), $case_sensitive = false)
10222:     {
10223:         $this->valid_values = array_flip($valid_values);
10224:         $this->case_sensitive = $case_sensitive;
10225:     }
10226: 
10227:     10228: 10229: 10230: 10231: 10232: 
10233:     public function validate($string, $config, $context)
10234:     {
10235:         $string = trim($string);
10236:         if (!$this->case_sensitive) {
10237:             
10238:             $string = ctype_lower($string) ? $string : strtolower($string);
10239:         }
10240:         $result = isset($this->valid_values[$string]);
10241: 
10242:         return $result ? $string : false;
10243:     }
10244: 
10245:     10246: 10247: 10248: 10249: 10250: 
10251:     public function make($string)
10252:     {
10253:         if (strlen($string) > 2 && $string[0] == 's' && $string[1] == ':') {
10254:             $string = substr($string, 2);
10255:             $sensitive = true;
10256:         } else {
10257:             $sensitive = false;
10258:         }
10259:         $values = explode(',', $string);
10260:         return new HTMLPurifier_AttrDef_Enum($values, $sensitive);
10261:     }
10262: }
10263: 
10264: 
10265: 
10266: 
10267: 
10268: 10269: 10270: 10271: 10272: 10273: 10274: 
10275: class HTMLPurifier_AttrDef_Integer extends HTMLPurifier_AttrDef
10276: {
10277: 
10278:     10279: 10280: 10281: 
10282:     protected $negative = true;
10283: 
10284:     10285: 10286: 10287: 
10288:     protected $zero = true;
10289: 
10290:     10291: 10292: 10293: 
10294:     protected $positive = true;
10295: 
10296:     10297: 10298: 10299: 10300: 
10301:     public function __construct($negative = true, $zero = true, $positive = true)
10302:     {
10303:         $this->negative = $negative;
10304:         $this->zero = $zero;
10305:         $this->positive = $positive;
10306:     }
10307: 
10308:     10309: 10310: 10311: 10312: 10313: 
10314:     public function validate($integer, $config, $context)
10315:     {
10316:         $integer = $this->parseCDATA($integer);
10317:         if ($integer === '') {
10318:             return false;
10319:         }
10320: 
10321:         
10322:         
10323: 
10324:         
10325:         if ($this->negative && $integer[0] === '-') {
10326:             $digits = substr($integer, 1);
10327:             if ($digits === '0') {
10328:                 $integer = '0';
10329:             } 
10330:         } elseif ($this->positive && $integer[0] === '+') {
10331:             $digits = $integer = substr($integer, 1); 
10332:         } else {
10333:             $digits = $integer;
10334:         }
10335: 
10336:         
10337:         if (!ctype_digit($digits)) {
10338:             return false;
10339:         }
10340: 
10341:         
10342:         if (!$this->zero && $integer == 0) {
10343:             return false;
10344:         }
10345:         if (!$this->positive && $integer > 0) {
10346:             return false;
10347:         }
10348:         if (!$this->negative && $integer < 0) {
10349:             return false;
10350:         }
10351: 
10352:         return $integer;
10353:     }
10354: }
10355: 
10356: 
10357: 
10358: 
10359: 
10360: 10361: 10362: 10363: 
10364: class HTMLPurifier_AttrDef_Lang extends HTMLPurifier_AttrDef
10365: {
10366: 
10367:     10368: 10369: 10370: 10371: 10372: 
10373:     public function validate($string, $config, $context)
10374:     {
10375:         $string = trim($string);
10376:         if (!$string) {
10377:             return false;
10378:         }
10379: 
10380:         $subtags = explode('-', $string);
10381:         $num_subtags = count($subtags);
10382: 
10383:         if ($num_subtags == 0) { 
10384:             return false;
10385:         }
10386: 
10387:         
10388:         $length = strlen($subtags[0]);
10389:         switch ($length) {
10390:             case 0:
10391:                 return false;
10392:             case 1:
10393:                 if (!($subtags[0] == 'x' || $subtags[0] == 'i')) {
10394:                     return false;
10395:                 }
10396:                 break;
10397:             case 2:
10398:             case 3:
10399:                 if (!ctype_alpha($subtags[0])) {
10400:                     return false;
10401:                 } elseif (!ctype_lower($subtags[0])) {
10402:                     $subtags[0] = strtolower($subtags[0]);
10403:                 }
10404:                 break;
10405:             default:
10406:                 return false;
10407:         }
10408: 
10409:         $new_string = $subtags[0];
10410:         if ($num_subtags == 1) {
10411:             return $new_string;
10412:         }
10413: 
10414:         
10415:         $length = strlen($subtags[1]);
10416:         if ($length == 0 || ($length == 1 && $subtags[1] != 'x') || $length > 8 || !ctype_alnum($subtags[1])) {
10417:             return $new_string;
10418:         }
10419:         if (!ctype_lower($subtags[1])) {
10420:             $subtags[1] = strtolower($subtags[1]);
10421:         }
10422: 
10423:         $new_string .= '-' . $subtags[1];
10424:         if ($num_subtags == 2) {
10425:             return $new_string;
10426:         }
10427: 
10428:         
10429:         for ($i = 2; $i < $num_subtags; $i++) {
10430:             $length = strlen($subtags[$i]);
10431:             if ($length == 0 || $length > 8 || !ctype_alnum($subtags[$i])) {
10432:                 return $new_string;
10433:             }
10434:             if (!ctype_lower($subtags[$i])) {
10435:                 $subtags[$i] = strtolower($subtags[$i]);
10436:             }
10437:             $new_string .= '-' . $subtags[$i];
10438:         }
10439:         return $new_string;
10440:     }
10441: }
10442: 
10443: 
10444: 
10445: 
10446: 
10447: 10448: 10449: 
10450: class HTMLPurifier_AttrDef_Switch
10451: {
10452: 
10453:     10454: 10455: 
10456:     protected $tag;
10457: 
10458:     10459: 10460: 
10461:     protected $withTag;
10462: 
10463:     10464: 10465: 
10466:     protected $withoutTag;
10467: 
10468:     10469: 10470: 10471: 10472: 
10473:     public function __construct($tag, $with_tag, $without_tag)
10474:     {
10475:         $this->tag = $tag;
10476:         $this->withTag = $with_tag;
10477:         $this->withoutTag = $without_tag;
10478:     }
10479: 
10480:     10481: 10482: 10483: 10484: 10485: 
10486:     public function validate($string, $config, $context)
10487:     {
10488:         $token = $context->get('CurrentToken', true);
10489:         if (!$token || $token->name !== $this->tag) {
10490:             return $this->withoutTag->validate($string, $config, $context);
10491:         } else {
10492:             return $this->withTag->validate($string, $config, $context);
10493:         }
10494:     }
10495: }
10496: 
10497: 
10498: 
10499: 
10500: 
10501: 10502: 10503: 
10504: class HTMLPurifier_AttrDef_Text extends HTMLPurifier_AttrDef
10505: {
10506: 
10507:     10508: 10509: 10510: 10511: 10512: 
10513:     public function validate($string, $config, $context)
10514:     {
10515:         return $this->parseCDATA($string);
10516:     }
10517: }
10518: 
10519: 
10520: 
10521: 
10522: 
10523: 10524: 10525: 10526: 
10527: class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef
10528: {
10529: 
10530:     10531: 10532: 
10533:     protected $parser;
10534: 
10535:     10536: 10537: 
10538:     protected $embedsResource;
10539: 
10540:     10541: 10542: 
10543:     public function __construct($embeds_resource = false)
10544:     {
10545:         $this->parser = new HTMLPurifier_URIParser();
10546:         $this->embedsResource = (bool)$embeds_resource;
10547:     }
10548: 
10549:     10550: 10551: 10552: 
10553:     public function make($string)
10554:     {
10555:         $embeds = ($string === 'embedded');
10556:         return new HTMLPurifier_AttrDef_URI($embeds);
10557:     }
10558: 
10559:     10560: 10561: 10562: 10563: 10564: 
10565:     public function validate($uri, $config, $context)
10566:     {
10567:         if ($config->get('URI.Disable')) {
10568:             return false;
10569:         }
10570: 
10571:         $uri = $this->parseCDATA($uri);
10572: 
10573:         
10574:         $uri = $this->parser->parse($uri);
10575:         if ($uri === false) {
10576:             return false;
10577:         }
10578: 
10579:         
10580:         $context->register('EmbeddedURI', $this->embedsResource);
10581: 
10582:         $ok = false;
10583:         do {
10584: 
10585:             
10586:             $result = $uri->validate($config, $context);
10587:             if (!$result) {
10588:                 break;
10589:             }
10590: 
10591:             
10592:             $uri_def = $config->getDefinition('URI');
10593:             $result = $uri_def->filter($uri, $config, $context);
10594:             if (!$result) {
10595:                 break;
10596:             }
10597: 
10598:             
10599:             $scheme_obj = $uri->getSchemeObj($config, $context);
10600:             if (!$scheme_obj) {
10601:                 break;
10602:             }
10603:             if ($this->embedsResource && !$scheme_obj->browsable) {
10604:                 break;
10605:             }
10606:             $result = $scheme_obj->validate($uri, $config, $context);
10607:             if (!$result) {
10608:                 break;
10609:             }
10610: 
10611:             
10612:             $result = $uri_def->postFilter($uri, $config, $context);
10613:             if (!$result) {
10614:                 break;
10615:             }
10616: 
10617:             
10618:             $ok = true;
10619: 
10620:         } while (false);
10621: 
10622:         $context->destroy('EmbeddedURI');
10623:         if (!$ok) {
10624:             return false;
10625:         }
10626:         
10627:         return $uri->toString();
10628:     }
10629: }
10630: 
10631: 
10632: 
10633: 
10634: 
10635: 10636: 10637: 
10638: class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef
10639: {
10640: 
10641:     10642: 10643: 10644: 
10645:     protected $non_negative = false;
10646: 
10647:     10648: 10649: 
10650:     public function __construct($non_negative = false)
10651:     {
10652:         $this->non_negative = $non_negative;
10653:     }
10654: 
10655:     10656: 10657: 10658: 10659: 10660: 10661: 10662: 
10663:     public function validate($number, $config, $context)
10664:     {
10665:         $number = $this->parseCDATA($number);
10666: 
10667:         if ($number === '') {
10668:             return false;
10669:         }
10670:         if ($number === '0') {
10671:             return '0';
10672:         }
10673: 
10674:         $sign = '';
10675:         switch ($number[0]) {
10676:             case '-':
10677:                 if ($this->non_negative) {
10678:                     return false;
10679:                 }
10680:                 $sign = '-';
10681:             case '+':
10682:                 $number = substr($number, 1);
10683:         }
10684: 
10685:         if (ctype_digit($number)) {
10686:             $number = ltrim($number, '0');
10687:             return $number ? $sign . $number : '0';
10688:         }
10689: 
10690:         
10691:         if (strpos($number, '.') === false) {
10692:             return false;
10693:         }
10694: 
10695:         list($left, $right) = explode('.', $number, 2);
10696: 
10697:         if ($left === '' && $right === '') {
10698:             return false;
10699:         }
10700:         if ($left !== '' && !ctype_digit($left)) {
10701:             return false;
10702:         }
10703: 
10704:         $left = ltrim($left, '0');
10705:         $right = rtrim($right, '0');
10706: 
10707:         if ($right === '') {
10708:             return $left ? $sign . $left : '0';
10709:         } elseif (!ctype_digit($right)) {
10710:             return false;
10711:         }
10712:         return $sign . $left . '.' . $right;
10713:     }
10714: }
10715: 
10716: 
10717: 
10718: 
10719: 
10720: class HTMLPurifier_AttrDef_CSS_AlphaValue extends HTMLPurifier_AttrDef_CSS_Number
10721: {
10722: 
10723:     public function __construct()
10724:     {
10725:         parent::__construct(false); 
10726:     }
10727: 
10728:     10729: 10730: 10731: 10732: 10733: 
10734:     public function validate($number, $config, $context)
10735:     {
10736:         $result = parent::validate($number, $config, $context);
10737:         if ($result === false) {
10738:             return $result;
10739:         }
10740:         $float = (float)$result;
10741:         if ($float < 0.0) {
10742:             $result = '0';
10743:         }
10744:         if ($float > 1.0) {
10745:             $result = '1';
10746:         }
10747:         return $result;
10748:     }
10749: }
10750: 
10751: 
10752: 
10753: 
10754: 
10755: 10756: 10757: 10758: 
10759: class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef
10760: {
10761: 
10762:     10763: 10764: 10765: 10766: 
10767:     protected $info;
10768: 
10769:     10770: 10771: 
10772:     public function __construct($config)
10773:     {
10774:         $def = $config->getCSSDefinition();
10775:         $this->info['background-color'] = $def->info['background-color'];
10776:         $this->info['background-image'] = $def->info['background-image'];
10777:         $this->info['background-repeat'] = $def->info['background-repeat'];
10778:         $this->info['background-attachment'] = $def->info['background-attachment'];
10779:         $this->info['background-position'] = $def->info['background-position'];
10780:     }
10781: 
10782:     10783: 10784: 10785: 10786: 10787: 
10788:     public function validate($string, $config, $context)
10789:     {
10790:         
10791:         $string = $this->parseCDATA($string);
10792:         if ($string === '') {
10793:             return false;
10794:         }
10795: 
10796:         
10797:         $string = $this->mungeRgb($string);
10798: 
10799:         
10800:         $bits = explode(' ', $string); 
10801: 
10802:         $caught = array();
10803:         $caught['color'] = false;
10804:         $caught['image'] = false;
10805:         $caught['repeat'] = false;
10806:         $caught['attachment'] = false;
10807:         $caught['position'] = false;
10808: 
10809:         $i = 0; 
10810: 
10811:         foreach ($bits as $bit) {
10812:             if ($bit === '') {
10813:                 continue;
10814:             }
10815:             foreach ($caught as $key => $status) {
10816:                 if ($key != 'position') {
10817:                     if ($status !== false) {
10818:                         continue;
10819:                     }
10820:                     $r = $this->info['background-' . $key]->validate($bit, $config, $context);
10821:                 } else {
10822:                     $r = $bit;
10823:                 }
10824:                 if ($r === false) {
10825:                     continue;
10826:                 }
10827:                 if ($key == 'position') {
10828:                     if ($caught[$key] === false) {
10829:                         $caught[$key] = '';
10830:                     }
10831:                     $caught[$key] .= $r . ' ';
10832:                 } else {
10833:                     $caught[$key] = $r;
10834:                 }
10835:                 $i++;
10836:                 break;
10837:             }
10838:         }
10839: 
10840:         if (!$i) {
10841:             return false;
10842:         }
10843:         if ($caught['position'] !== false) {
10844:             $caught['position'] = $this->info['background-position']->
10845:                 validate($caught['position'], $config, $context);
10846:         }
10847: 
10848:         $ret = array();
10849:         foreach ($caught as $value) {
10850:             if ($value === false) {
10851:                 continue;
10852:             }
10853:             $ret[] = $value;
10854:         }
10855: 
10856:         if (empty($ret)) {
10857:             return false;
10858:         }
10859:         return implode(' ', $ret);
10860:     }
10861: }
10862: 
10863: 
10864: 
10865: 
10866: 
10867: 10868: 10869: 10870: 10871: 10872: 10873: 10874: 10875: 10876: 10877: 10878: 10879: 10880: 10881: 10882: 10883: 10884: 10885: 10886: 10887: 10888: 10889: 10890: 10891: 
10892: 
10893: 10894: 10895: 10896: 10897: 10898: 10899: 10900: 
10901: 
10902: 
10903: 
10904: 
10905: 10906: 10907: 
10908: class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef
10909: {
10910: 
10911:     10912: 10913: 
10914:     protected $length;
10915: 
10916:     10917: 10918: 
10919:     protected $percentage;
10920: 
10921:     public function __construct()
10922:     {
10923:         $this->length = new HTMLPurifier_AttrDef_CSS_Length();
10924:         $this->percentage = new HTMLPurifier_AttrDef_CSS_Percentage();
10925:     }
10926: 
10927:     10928: 10929: 10930: 10931: 10932: 
10933:     public function validate($string, $config, $context)
10934:     {
10935:         $string = $this->parseCDATA($string);
10936:         $bits = explode(' ', $string);
10937: 
10938:         $keywords = array();
10939:         $keywords['h'] = false; 
10940:         $keywords['v'] = false; 
10941:         $keywords['ch'] = false; 
10942:         $keywords['cv'] = false; 
10943:         $measures = array();
10944: 
10945:         $i = 0;
10946: 
10947:         $lookup = array(
10948:             'top' => 'v',
10949:             'bottom' => 'v',
10950:             'left' => 'h',
10951:             'right' => 'h',
10952:             'center' => 'c'
10953:         );
10954: 
10955:         foreach ($bits as $bit) {
10956:             if ($bit === '') {
10957:                 continue;
10958:             }
10959: 
10960:             
10961:             $lbit = ctype_lower($bit) ? $bit : strtolower($bit);
10962:             if (isset($lookup[$lbit])) {
10963:                 $status = $lookup[$lbit];
10964:                 if ($status == 'c') {
10965:                     if ($i == 0) {
10966:                         $status = 'ch';
10967:                     } else {
10968:                         $status = 'cv';
10969:                     }
10970:                 }
10971:                 $keywords[$status] = $lbit;
10972:                 $i++;
10973:             }
10974: 
10975:             
10976:             $r = $this->length->validate($bit, $config, $context);
10977:             if ($r !== false) {
10978:                 $measures[] = $r;
10979:                 $i++;
10980:             }
10981: 
10982:             
10983:             $r = $this->percentage->validate($bit, $config, $context);
10984:             if ($r !== false) {
10985:                 $measures[] = $r;
10986:                 $i++;
10987:             }
10988:         }
10989: 
10990:         if (!$i) {
10991:             return false;
10992:         } 
10993: 
10994:         $ret = array();
10995: 
10996:         
10997:         if ($keywords['h']) {
10998:             $ret[] = $keywords['h'];
10999:         } elseif ($keywords['ch']) {
11000:             $ret[] = $keywords['ch'];
11001:             $keywords['cv'] = false; 
11002:         } elseif (count($measures)) {
11003:             $ret[] = array_shift($measures);
11004:         }
11005: 
11006:         if ($keywords['v']) {
11007:             $ret[] = $keywords['v'];
11008:         } elseif ($keywords['cv']) {
11009:             $ret[] = $keywords['cv'];
11010:         } elseif (count($measures)) {
11011:             $ret[] = array_shift($measures);
11012:         }
11013: 
11014:         if (empty($ret)) {
11015:             return false;
11016:         }
11017:         return implode(' ', $ret);
11018:     }
11019: }
11020: 
11021: 
11022: 
11023: 
11024: 
11025: 11026: 11027: 
11028: class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef
11029: {
11030: 
11031:     11032: 11033: 11034: 
11035:     protected $info = array();
11036: 
11037:     11038: 11039: 
11040:     public function __construct($config)
11041:     {
11042:         $def = $config->getCSSDefinition();
11043:         $this->info['border-width'] = $def->info['border-width'];
11044:         $this->info['border-style'] = $def->info['border-style'];
11045:         $this->info['border-top-color'] = $def->info['border-top-color'];
11046:     }
11047: 
11048:     11049: 11050: 11051: 11052: 11053: 
11054:     public function validate($string, $config, $context)
11055:     {
11056:         $string = $this->parseCDATA($string);
11057:         $string = $this->mungeRgb($string);
11058:         $bits = explode(' ', $string);
11059:         $done = array(); 
11060:         $ret = ''; 
11061:         foreach ($bits as $bit) {
11062:             foreach ($this->info as $propname => $validator) {
11063:                 if (isset($done[$propname])) {
11064:                     continue;
11065:                 }
11066:                 $r = $validator->validate($bit, $config, $context);
11067:                 if ($r !== false) {
11068:                     $ret .= $r . ' ';
11069:                     $done[$propname] = true;
11070:                     break;
11071:                 }
11072:             }
11073:         }
11074:         return rtrim($ret);
11075:     }
11076: }
11077: 
11078: 
11079: 
11080: 
11081: 
11082: 11083: 11084: 
11085: class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
11086: {
11087: 
11088:     11089: 11090: 11091: 11092: 11093: 
11094:     public function validate($color, $config, $context)
11095:     {
11096:         static $colors = null;
11097:         if ($colors === null) {
11098:             $colors = $config->get('Core.ColorKeywords');
11099:         }
11100: 
11101:         $color = trim($color);
11102:         if ($color === '') {
11103:             return false;
11104:         }
11105: 
11106:         $lower = strtolower($color);
11107:         if (isset($colors[$lower])) {
11108:             return $colors[$lower];
11109:         }
11110: 
11111:         if (strpos($color, 'rgb(') !== false) {
11112:             
11113:             $length = strlen($color);
11114:             if (strpos($color, ')') !== $length - 1) {
11115:                 return false;
11116:             }
11117:             $triad = substr($color, 4, $length - 4 - 1);
11118:             $parts = explode(',', $triad);
11119:             if (count($parts) !== 3) {
11120:                 return false;
11121:             }
11122:             $type = false; 
11123:             $new_parts = array();
11124:             foreach ($parts as $part) {
11125:                 $part = trim($part);
11126:                 if ($part === '') {
11127:                     return false;
11128:                 }
11129:                 $length = strlen($part);
11130:                 if ($part[$length - 1] === '%') {
11131:                     
11132:                     if (!$type) {
11133:                         $type = 'percentage';
11134:                     } elseif ($type !== 'percentage') {
11135:                         return false;
11136:                     }
11137:                     $num = (float)substr($part, 0, $length - 1);
11138:                     if ($num < 0) {
11139:                         $num = 0;
11140:                     }
11141:                     if ($num > 100) {
11142:                         $num = 100;
11143:                     }
11144:                     $new_parts[] = "$num%";
11145:                 } else {
11146:                     
11147:                     if (!$type) {
11148:                         $type = 'integer';
11149:                     } elseif ($type !== 'integer') {
11150:                         return false;
11151:                     }
11152:                     $num = (int)$part;
11153:                     if ($num < 0) {
11154:                         $num = 0;
11155:                     }
11156:                     if ($num > 255) {
11157:                         $num = 255;
11158:                     }
11159:                     $new_parts[] = (string)$num;
11160:                 }
11161:             }
11162:             $new_triad = implode(',', $new_parts);
11163:             $color = "rgb($new_triad)";
11164:         } else {
11165:             
11166:             if ($color[0] === '#') {
11167:                 $hex = substr($color, 1);
11168:             } else {
11169:                 $hex = $color;
11170:                 $color = '#' . $color;
11171:             }
11172:             $length = strlen($hex);
11173:             if ($length !== 3 && $length !== 6) {
11174:                 return false;
11175:             }
11176:             if (!ctype_xdigit($hex)) {
11177:                 return false;
11178:             }
11179:         }
11180:         return $color;
11181:     }
11182: }
11183: 
11184: 
11185: 
11186: 
11187: 
11188: 11189: 11190: 11191: 11192: 11193: 11194: 11195: 11196: 
11197: class HTMLPurifier_AttrDef_CSS_Composite extends HTMLPurifier_AttrDef
11198: {
11199: 
11200:     11201: 11202: 11203: 11204: 
11205:     public $defs;
11206: 
11207:     11208: 11209: 
11210:     public function __construct($defs)
11211:     {
11212:         $this->defs = $defs;
11213:     }
11214: 
11215:     11216: 11217: 11218: 11219: 11220: 
11221:     public function validate($string, $config, $context)
11222:     {
11223:         foreach ($this->defs as $i => $def) {
11224:             $result = $this->defs[$i]->validate($string, $config, $context);
11225:             if ($result !== false) {
11226:                 return $result;
11227:             }
11228:         }
11229:         return false;
11230:     }
11231: }
11232: 
11233: 
11234: 
11235: 
11236: 
11237: 11238: 11239: 
11240: class HTMLPurifier_AttrDef_CSS_DenyElementDecorator extends HTMLPurifier_AttrDef
11241: {
11242:     11243: 11244: 
11245:     public $def;
11246:     11247: 11248: 
11249:     public $element;
11250: 
11251:     11252: 11253: 11254: 
11255:     public function __construct($def, $element)
11256:     {
11257:         $this->def = $def;
11258:         $this->element = $element;
11259:     }
11260: 
11261:     11262: 11263: 11264: 11265: 11266: 11267: 
11268:     public function validate($string, $config, $context)
11269:     {
11270:         $token = $context->get('CurrentToken', true);
11271:         if ($token && $token->name == $this->element) {
11272:             return false;
11273:         }
11274:         return $this->def->validate($string, $config, $context);
11275:     }
11276: }
11277: 
11278: 
11279: 
11280: 
11281: 
11282: 11283: 11284: 11285: 11286: 
11287: class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef
11288: {
11289:     11290: 11291: 
11292:     protected $intValidator;
11293: 
11294:     public function __construct()
11295:     {
11296:         $this->intValidator = new HTMLPurifier_AttrDef_Integer();
11297:     }
11298: 
11299:     11300: 11301: 11302: 11303: 11304: 
11305:     public function validate($value, $config, $context)
11306:     {
11307:         $value = $this->parseCDATA($value);
11308:         if ($value === 'none') {
11309:             return $value;
11310:         }
11311:         
11312:         $function_length = strcspn($value, '(');
11313:         $function = trim(substr($value, 0, $function_length));
11314:         if ($function !== 'alpha' &&
11315:             $function !== 'Alpha' &&
11316:             $function !== 'progid:DXImageTransform.Microsoft.Alpha'
11317:         ) {
11318:             return false;
11319:         }
11320:         $cursor = $function_length + 1;
11321:         $parameters_length = strcspn($value, ')', $cursor);
11322:         $parameters = substr($value, $cursor, $parameters_length);
11323:         $params = explode(',', $parameters);
11324:         $ret_params = array();
11325:         $lookup = array();
11326:         foreach ($params as $param) {
11327:             list($key, $value) = explode('=', $param);
11328:             $key = trim($key);
11329:             $value = trim($value);
11330:             if (isset($lookup[$key])) {
11331:                 continue;
11332:             }
11333:             if ($key !== 'opacity') {
11334:                 continue;
11335:             }
11336:             $value = $this->intValidator->validate($value, $config, $context);
11337:             if ($value === false) {
11338:                 continue;
11339:             }
11340:             $int = (int)$value;
11341:             if ($int > 100) {
11342:                 $value = '100';
11343:             }
11344:             if ($int < 0) {
11345:                 $value = '0';
11346:             }
11347:             $ret_params[] = "$key=$value";
11348:             $lookup[$key] = true;
11349:         }
11350:         $ret_parameters = implode(',', $ret_params);
11351:         $ret_function = "$function($ret_parameters)";
11352:         return $ret_function;
11353:     }
11354: }
11355: 
11356: 
11357: 
11358: 
11359: 
11360: 11361: 11362: 
11363: class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
11364: {
11365: 
11366:     11367: 11368: 11369: 11370: 11371: 11372: 11373: 
11374:     protected $info = array();
11375: 
11376:     11377: 11378: 
11379:     public function __construct($config)
11380:     {
11381:         $def = $config->getCSSDefinition();
11382:         $this->info['font-style'] = $def->info['font-style'];
11383:         $this->info['font-variant'] = $def->info['font-variant'];
11384:         $this->info['font-weight'] = $def->info['font-weight'];
11385:         $this->info['font-size'] = $def->info['font-size'];
11386:         $this->info['line-height'] = $def->info['line-height'];
11387:         $this->info['font-family'] = $def->info['font-family'];
11388:     }
11389: 
11390:     11391: 11392: 11393: 11394: 11395: 
11396:     public function validate($string, $config, $context)
11397:     {
11398:         static $system_fonts = array(
11399:             'caption' => true,
11400:             'icon' => true,
11401:             'menu' => true,
11402:             'message-box' => true,
11403:             'small-caption' => true,
11404:             'status-bar' => true
11405:         );
11406: 
11407:         
11408:         $string = $this->parseCDATA($string);
11409:         if ($string === '') {
11410:             return false;
11411:         }
11412: 
11413:         
11414:         $lowercase_string = strtolower($string);
11415:         if (isset($system_fonts[$lowercase_string])) {
11416:             return $lowercase_string;
11417:         }
11418: 
11419:         $bits = explode(' ', $string); 
11420:         $stage = 0; 
11421:         $caught = array(); 
11422:         $stage_1 = array('font-style', 'font-variant', 'font-weight');
11423:         $final = ''; 
11424: 
11425:         for ($i = 0, $size = count($bits); $i < $size; $i++) {
11426:             if ($bits[$i] === '') {
11427:                 continue;
11428:             }
11429:             switch ($stage) {
11430:                 case 0: 
11431:                     foreach ($stage_1 as $validator_name) {
11432:                         if (isset($caught[$validator_name])) {
11433:                             continue;
11434:                         }
11435:                         $r = $this->info[$validator_name]->validate(
11436:                             $bits[$i],
11437:                             $config,
11438:                             $context
11439:                         );
11440:                         if ($r !== false) {
11441:                             $final .= $r . ' ';
11442:                             $caught[$validator_name] = true;
11443:                             break;
11444:                         }
11445:                     }
11446:                     
11447:                     if (count($caught) >= 3) {
11448:                         $stage = 1;
11449:                     }
11450:                     if ($r !== false) {
11451:                         break;
11452:                     }
11453:                 case 1: 
11454:                     $found_slash = false;
11455:                     if (strpos($bits[$i], '/') !== false) {
11456:                         list($font_size, $line_height) =
11457:                             explode('/', $bits[$i]);
11458:                         if ($line_height === '') {
11459:                             
11460:                             $line_height = false;
11461:                             $found_slash = true;
11462:                         }
11463:                     } else {
11464:                         $font_size = $bits[$i];
11465:                         $line_height = false;
11466:                     }
11467:                     $r = $this->info['font-size']->validate(
11468:                         $font_size,
11469:                         $config,
11470:                         $context
11471:                     );
11472:                     if ($r !== false) {
11473:                         $final .= $r;
11474:                         
11475:                         if ($line_height === false) {
11476:                             
11477:                             for ($j = $i + 1; $j < $size; $j++) {
11478:                                 if ($bits[$j] === '') {
11479:                                     continue;
11480:                                 }
11481:                                 if ($bits[$j] === '/') {
11482:                                     if ($found_slash) {
11483:                                         return false;
11484:                                     } else {
11485:                                         $found_slash = true;
11486:                                         continue;
11487:                                     }
11488:                                 }
11489:                                 $line_height = $bits[$j];
11490:                                 break;
11491:                             }
11492:                         } else {
11493:                             
11494:                             $found_slash = true;
11495:                             $j = $i;
11496:                         }
11497:                         if ($found_slash) {
11498:                             $i = $j;
11499:                             $r = $this->info['line-height']->validate(
11500:                                 $line_height,
11501:                                 $config,
11502:                                 $context
11503:                             );
11504:                             if ($r !== false) {
11505:                                 $final .= '/' . $r;
11506:                             }
11507:                         }
11508:                         $final .= ' ';
11509:                         $stage = 2;
11510:                         break;
11511:                     }
11512:                     return false;
11513:                 case 2: 
11514:                     $font_family =
11515:                         implode(' ', array_slice($bits, $i, $size - $i));
11516:                     $r = $this->info['font-family']->validate(
11517:                         $font_family,
11518:                         $config,
11519:                         $context
11520:                     );
11521:                     if ($r !== false) {
11522:                         $final .= $r . ' ';
11523:                         
11524:                         return rtrim($final);
11525:                     }
11526:                     return false;
11527:             }
11528:         }
11529:         return false;
11530:     }
11531: }
11532: 
11533: 
11534: 
11535: 
11536: 
11537: 11538: 11539: 
11540: class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
11541: {
11542: 
11543:     protected $mask = null;
11544: 
11545:     public function __construct()
11546:     {
11547:         $this->mask = '_- ';
11548:         for ($c = 'a'; $c <= 'z'; $c++) {
11549:             $this->mask .= $c;
11550:         }
11551:         for ($c = 'A'; $c <= 'Z'; $c++) {
11552:             $this->mask .= $c;
11553:         }
11554:         for ($c = '0'; $c <= '9'; $c++) {
11555:             $this->mask .= $c;
11556:         } 
11557:         
11558:         for ($i = 0x80; $i <= 0xFF; $i++) {
11559:             
11560:             
11561:             
11562:             $this->mask .= chr($i);
11563:         }
11564: 
11565:         11566: 11567: 11568: 11569: 11570: 11571: 11572: 11573: 11574: 11575: 11576: 11577: 11578: 11579: 
11580:         
11581:     }
11582: 
11583:     11584: 11585: 11586: 11587: 11588: 
11589:     public function validate($string, $config, $context)
11590:     {
11591:         static $generic_names = array(
11592:             'serif' => true,
11593:             'sans-serif' => true,
11594:             'monospace' => true,
11595:             'fantasy' => true,
11596:             'cursive' => true
11597:         );
11598:         $allowed_fonts = $config->get('CSS.AllowedFonts');
11599: 
11600:         
11601:         $fonts = explode(',', $string);
11602:         $final = '';
11603:         foreach ($fonts as $font) {
11604:             $font = trim($font);
11605:             if ($font === '') {
11606:                 continue;
11607:             }
11608:             
11609:             if (isset($generic_names[$font])) {
11610:                 if ($allowed_fonts === null || isset($allowed_fonts[$font])) {
11611:                     $final .= $font . ', ';
11612:                 }
11613:                 continue;
11614:             }
11615:             
11616:             if ($font[0] === '"' || $font[0] === "'") {
11617:                 $length = strlen($font);
11618:                 if ($length <= 2) {
11619:                     continue;
11620:                 }
11621:                 $quote = $font[0];
11622:                 if ($font[$length - 1] !== $quote) {
11623:                     continue;
11624:                 }
11625:                 $font = substr($font, 1, $length - 2);
11626:             }
11627: 
11628:             $font = $this->expandCSSEscape($font);
11629: 
11630:             
11631: 
11632:             if ($allowed_fonts !== null && !isset($allowed_fonts[$font])) {
11633:                 continue;
11634:             }
11635: 
11636:             if (ctype_alnum($font) && $font !== '') {
11637:                 
11638:                 $final .= $font . ', ';
11639:                 continue;
11640:             }
11641: 
11642:             
11643:             
11644:             $font = str_replace(array("\n", "\t", "\r", "\x0C"), ' ', $font);
11645: 
11646:             
11647:             
11648:             
11649:             
11650:             
11651:             
11652:             
11653:             
11654:             
11655:             
11656:             
11657:             
11658:             
11659:             
11660:             
11661:             
11662:             
11663:             
11664:             
11665:             
11666:             
11667:             
11668:             
11669:             
11670:             
11671:             
11672:             
11673:             
11674:             
11675:             
11676:             
11677:             
11678:             
11679:             
11680:             
11681:             
11682:             
11683:             
11684:             
11685:             
11686:             
11687:             
11688:             
11689:             
11690:             
11691:             
11692:             
11693:             
11694:             
11695:             
11696:             
11697:             
11698:             
11699:             
11700:             
11701:             
11702:             
11703:             
11704:             
11705:             
11706:             
11707:             
11708:             
11709:             
11710:             
11711:             
11712:             
11713:             
11714:             
11715:             
11716:             
11717:             
11718:             
11719:             
11720:             
11721: 
11722:             
11723:             
11724:             
11725:             if (strspn($font, $this->mask) !== strlen($font)) {
11726:                 continue;
11727:             }
11728: 
11729:             
11730:             
11731:             
11732:             
11733:             
11734:             
11735:             
11736:             
11737:             
11738:             
11739:             
11740: 
11741:             
11742:             $final .= "'$font', ";
11743:         }
11744:         $final = rtrim($final, ', ');
11745:         if ($final === '') {
11746:             return false;
11747:         }
11748:         return $final;
11749:     }
11750: 
11751: }
11752: 
11753: 
11754: 
11755: 
11756: 
11757: 11758: 11759: 
11760: class HTMLPurifier_AttrDef_CSS_Ident extends HTMLPurifier_AttrDef
11761: {
11762: 
11763:     11764: 11765: 11766: 11767: 11768: 
11769:     public function validate($string, $config, $context)
11770:     {
11771:         $string = trim($string);
11772: 
11773:         
11774:         if (!$string) {
11775:             return false;
11776:         }
11777: 
11778:         $pattern = '/^(-?[A-Za-z_][A-Za-z_\-0-9]*)$/';
11779:         if (!preg_match($pattern, $string)) {
11780:             return false;
11781:         }
11782:         return $string;
11783:     }
11784: }
11785: 
11786: 
11787: 
11788: 
11789: 
11790: 11791: 11792: 
11793: class HTMLPurifier_AttrDef_CSS_ImportantDecorator extends HTMLPurifier_AttrDef
11794: {
11795:     11796: 11797: 
11798:     public $def;
11799:     11800: 11801: 
11802:     public $allow;
11803: 
11804:     11805: 11806: 11807: 
11808:     public function __construct($def, $allow = false)
11809:     {
11810:         $this->def = $def;
11811:         $this->allow = $allow;
11812:     }
11813: 
11814:     11815: 11816: 11817: 11818: 11819: 11820: 
11821:     public function validate($string, $config, $context)
11822:     {
11823:         
11824:         $string = trim($string);
11825:         $is_important = false;
11826:         
11827:         if (strlen($string) >= 9 && substr($string, -9) === 'important') {
11828:             $temp = rtrim(substr($string, 0, -9));
11829:             
11830:             if (strlen($temp) >= 1 && substr($temp, -1) === '!') {
11831:                 $string = rtrim(substr($temp, 0, -1));
11832:                 $is_important = true;
11833:             }
11834:         }
11835:         $string = $this->def->validate($string, $config, $context);
11836:         if ($this->allow && $is_important) {
11837:             $string .= ' !important';
11838:         }
11839:         return $string;
11840:     }
11841: }
11842: 
11843: 
11844: 
11845: 
11846: 
11847: 11848: 11849: 
11850: class HTMLPurifier_AttrDef_CSS_Length extends HTMLPurifier_AttrDef
11851: {
11852: 
11853:     11854: 11855: 
11856:     protected $min;
11857: 
11858:     11859: 11860: 
11861:     protected $max;
11862: 
11863:     11864: 11865: 11866: 
11867:     public function __construct($min = null, $max = null)
11868:     {
11869:         $this->min = $min !== null ? HTMLPurifier_Length::make($min) : null;
11870:         $this->max = $max !== null ? HTMLPurifier_Length::make($max) : null;
11871:     }
11872: 
11873:     11874: 11875: 11876: 11877: 11878: 
11879:     public function validate($string, $config, $context)
11880:     {
11881:         $string = $this->parseCDATA($string);
11882: 
11883:         
11884:         if ($string === '') {
11885:             return false;
11886:         }
11887:         if ($string === '0') {
11888:             return '0';
11889:         }
11890:         if (strlen($string) === 1) {
11891:             return false;
11892:         }
11893: 
11894:         $length = HTMLPurifier_Length::make($string);
11895:         if (!$length->isValid()) {
11896:             return false;
11897:         }
11898: 
11899:         if ($this->min) {
11900:             $c = $length->compareTo($this->min);
11901:             if ($c === false) {
11902:                 return false;
11903:             }
11904:             if ($c < 0) {
11905:                 return false;
11906:             }
11907:         }
11908:         if ($this->max) {
11909:             $c = $length->compareTo($this->max);
11910:             if ($c === false) {
11911:                 return false;
11912:             }
11913:             if ($c > 0) {
11914:                 return false;
11915:             }
11916:         }
11917:         return $length->toString();
11918:     }
11919: }
11920: 
11921: 
11922: 
11923: 
11924: 
11925: 11926: 11927: 11928: 
11929: class HTMLPurifier_AttrDef_CSS_ListStyle extends HTMLPurifier_AttrDef
11930: {
11931: 
11932:     11933: 11934: 11935: 11936: 
11937:     protected $info;
11938: 
11939:     11940: 11941: 
11942:     public function __construct($config)
11943:     {
11944:         $def = $config->getCSSDefinition();
11945:         $this->info['list-style-type'] = $def->info['list-style-type'];
11946:         $this->info['list-style-position'] = $def->info['list-style-position'];
11947:         $this->info['list-style-image'] = $def->info['list-style-image'];
11948:     }
11949: 
11950:     11951: 11952: 11953: 11954: 11955: 
11956:     public function validate($string, $config, $context)
11957:     {
11958:         
11959:         $string = $this->parseCDATA($string);
11960:         if ($string === '') {
11961:             return false;
11962:         }
11963: 
11964:         
11965:         $bits = explode(' ', strtolower($string)); 
11966: 
11967:         $caught = array();
11968:         $caught['type'] = false;
11969:         $caught['position'] = false;
11970:         $caught['image'] = false;
11971: 
11972:         $i = 0; 
11973:         $none = false;
11974: 
11975:         foreach ($bits as $bit) {
11976:             if ($i >= 3) {
11977:                 return;
11978:             } 
11979:             if ($bit === '') {
11980:                 continue;
11981:             }
11982:             foreach ($caught as $key => $status) {
11983:                 if ($status !== false) {
11984:                     continue;
11985:                 }
11986:                 $r = $this->info['list-style-' . $key]->validate($bit, $config, $context);
11987:                 if ($r === false) {
11988:                     continue;
11989:                 }
11990:                 if ($r === 'none') {
11991:                     if ($none) {
11992:                         continue;
11993:                     } else {
11994:                         $none = true;
11995:                     }
11996:                     if ($key == 'image') {
11997:                         continue;
11998:                     }
11999:                 }
12000:                 $caught[$key] = $r;
12001:                 $i++;
12002:                 break;
12003:             }
12004:         }
12005: 
12006:         if (!$i) {
12007:             return false;
12008:         }
12009: 
12010:         $ret = array();
12011: 
12012:         
12013:         if ($caught['type']) {
12014:             $ret[] = $caught['type'];
12015:         }
12016: 
12017:         
12018:         if ($caught['image']) {
12019:             $ret[] = $caught['image'];
12020:         }
12021: 
12022:         
12023:         if ($caught['position']) {
12024:             $ret[] = $caught['position'];
12025:         }
12026: 
12027:         if (empty($ret)) {
12028:             return false;
12029:         }
12030:         return implode(' ', $ret);
12031:     }
12032: }
12033: 
12034: 
12035: 
12036: 
12037: 
12038: 12039: 12040: 12041: 12042: 12043: 12044: 12045: 12046: 12047: 12048: 
12049: class HTMLPurifier_AttrDef_CSS_Multiple extends HTMLPurifier_AttrDef
12050: {
12051:     12052: 12053: 12054: 12055: 
12056:     public $single;
12057: 
12058:     12059: 12060: 12061: 
12062:     public $max;
12063: 
12064:     12065: 12066: 12067: 
12068:     public function __construct($single, $max = 4)
12069:     {
12070:         $this->single = $single;
12071:         $this->max = $max;
12072:     }
12073: 
12074:     12075: 12076: 12077: 12078: 12079: 
12080:     public function validate($string, $config, $context)
12081:     {
12082:         $string = $this->parseCDATA($string);
12083:         if ($string === '') {
12084:             return false;
12085:         }
12086:         $parts = explode(' ', $string); 
12087:         $length = count($parts);
12088:         $final = '';
12089:         for ($i = 0, $num = 0; $i < $length && $num < $this->max; $i++) {
12090:             if (ctype_space($parts[$i])) {
12091:                 continue;
12092:             }
12093:             $result = $this->single->validate($parts[$i], $config, $context);
12094:             if ($result !== false) {
12095:                 $final .= $result . ' ';
12096:                 $num++;
12097:             }
12098:         }
12099:         if ($final === '') {
12100:             return false;
12101:         }
12102:         return rtrim($final);
12103:     }
12104: }
12105: 
12106: 
12107: 
12108: 
12109: 
12110: 12111: 12112: 
12113: class HTMLPurifier_AttrDef_CSS_Percentage extends HTMLPurifier_AttrDef
12114: {
12115: 
12116:     12117: 12118: 12119: 
12120:     protected $number_def;
12121: 
12122:     12123: 12124: 
12125:     public function __construct($non_negative = false)
12126:     {
12127:         $this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative);
12128:     }
12129: 
12130:     12131: 12132: 12133: 12134: 12135: 
12136:     public function validate($string, $config, $context)
12137:     {
12138:         $string = $this->parseCDATA($string);
12139: 
12140:         if ($string === '') {
12141:             return false;
12142:         }
12143:         $length = strlen($string);
12144:         if ($length === 1) {
12145:             return false;
12146:         }
12147:         if ($string[$length - 1] !== '%') {
12148:             return false;
12149:         }
12150: 
12151:         $number = substr($string, 0, $length - 1);
12152:         $number = $this->number_def->validate($number, $config, $context);
12153: 
12154:         if ($number === false) {
12155:             return false;
12156:         }
12157:         return "$number%";
12158:     }
12159: }
12160: 
12161: 
12162: 
12163: 
12164: 
12165: 12166: 12167: 12168: 12169: 
12170: class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef
12171: {
12172: 
12173:     12174: 12175: 12176: 12177: 12178: 
12179:     public function validate($string, $config, $context)
12180:     {
12181:         static $allowed_values = array(
12182:             'line-through' => true,
12183:             'overline' => true,
12184:             'underline' => true,
12185:         );
12186: 
12187:         $string = strtolower($this->parseCDATA($string));
12188: 
12189:         if ($string === 'none') {
12190:             return $string;
12191:         }
12192: 
12193:         $parts = explode(' ', $string);
12194:         $final = '';
12195:         foreach ($parts as $part) {
12196:             if (isset($allowed_values[$part])) {
12197:                 $final .= $part . ' ';
12198:             }
12199:         }
12200:         $final = rtrim($final);
12201:         if ($final === '') {
12202:             return false;
12203:         }
12204:         return $final;
12205:     }
12206: }
12207: 
12208: 
12209: 
12210: 
12211: 
12212: 12213: 12214: 12215: 12216: 12217: 12218: 12219: 12220: 
12221: class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI
12222: {
12223: 
12224:     public function __construct()
12225:     {
12226:         parent::__construct(true); 
12227:     }
12228: 
12229:     12230: 12231: 12232: 12233: 12234: 
12235:     public function validate($uri_string, $config, $context)
12236:     {
12237:         
12238:         
12239: 
12240:         $uri_string = $this->parseCDATA($uri_string);
12241:         if (strpos($uri_string, 'url(') !== 0) {
12242:             return false;
12243:         }
12244:         $uri_string = substr($uri_string, 4);
12245:         $new_length = strlen($uri_string) - 1;
12246:         if ($uri_string[$new_length] != ')') {
12247:             return false;
12248:         }
12249:         $uri = trim(substr($uri_string, 0, $new_length));
12250: 
12251:         if (!empty($uri) && ($uri[0] == "'" || $uri[0] == '"')) {
12252:             $quote = $uri[0];
12253:             $new_length = strlen($uri) - 1;
12254:             if ($uri[$new_length] !== $quote) {
12255:                 return false;
12256:             }
12257:             $uri = substr($uri, 1, $new_length - 1);
12258:         }
12259: 
12260:         $uri = $this->expandCSSEscape($uri);
12261: 
12262:         $result = parent::validate($uri, $config, $context);
12263: 
12264:         if ($result === false) {
12265:             return false;
12266:         }
12267: 
12268:         
12269:         $result = str_replace(array('"', "\\", "\n", "\x0c", "\r"), "", $result);
12270: 
12271:         
12272:         
12273:         $result = str_replace(array('(', ')', "'"), array('%28', '%29', '%27'), $result);
12274: 
12275:         
12276:         
12277:         
12278:         
12279:         return "url(\"$result\")";
12280:     }
12281: }
12282: 
12283: 
12284: 
12285: 
12286: 
12287: 12288: 12289: 
12290: class HTMLPurifier_AttrDef_HTML_Bool extends HTMLPurifier_AttrDef
12291: {
12292: 
12293:     12294: 12295: 
12296:     protected $name;
12297: 
12298:     12299: 12300: 
12301:     public $minimized = true;
12302: 
12303:     12304: 12305: 
12306:     public function __construct($name = false)
12307:     {
12308:         $this->name = $name;
12309:     }
12310: 
12311:     12312: 12313: 12314: 12315: 12316: 
12317:     public function validate($string, $config, $context)
12318:     {
12319:         if (empty($string)) {
12320:             return false;
12321:         }
12322:         return $this->name;
12323:     }
12324: 
12325:     12326: 12327: 12328: 
12329:     public function make($string)
12330:     {
12331:         return new HTMLPurifier_AttrDef_HTML_Bool($string);
12332:     }
12333: }
12334: 
12335: 
12336: 
12337: 
12338: 
12339: 12340: 12341: 
12342: class HTMLPurifier_AttrDef_HTML_Nmtokens extends HTMLPurifier_AttrDef
12343: {
12344: 
12345:     12346: 12347: 12348: 12349: 12350: 
12351:     public function validate($string, $config, $context)
12352:     {
12353:         $string = trim($string);
12354: 
12355:         
12356:         if (!$string) {
12357:             return false;
12358:         }
12359: 
12360:         $tokens = $this->split($string, $config, $context);
12361:         $tokens = $this->filter($tokens, $config, $context);
12362:         if (empty($tokens)) {
12363:             return false;
12364:         }
12365:         return implode(' ', $tokens);
12366:     }
12367: 
12368:     12369: 12370: 12371: 12372: 12373: 12374: 
12375:     protected function split($string, $config, $context)
12376:     {
12377:         
12378:         
12379: 
12380:         
12381:         
12382:         
12383:         
12384:         $pattern = '/(?:(?<=\s)|\A)' . 
12385:             '((?:--|-?[A-Za-z_])[A-Za-z_\-0-9]*)' .
12386:             '(?:(?=\s)|\z)/'; 
12387:         preg_match_all($pattern, $string, $matches);
12388:         return $matches[1];
12389:     }
12390: 
12391:     12392: 12393: 12394: 12395: 12396: 12397: 12398: 12399: 
12400:     protected function filter($tokens, $config, $context)
12401:     {
12402:         return $tokens;
12403:     }
12404: }
12405: 
12406: 
12407: 
12408: 
12409: 
12410: 12411: 12412: 
12413: class HTMLPurifier_AttrDef_HTML_Class extends HTMLPurifier_AttrDef_HTML_Nmtokens
12414: {
12415:     12416: 12417: 12418: 12419: 12420: 
12421:     protected function split($string, $config, $context)
12422:     {
12423:         
12424:         $name = $config->getDefinition('HTML')->doctype->name;
12425:         if ($name == "XHTML 1.1" || $name == "XHTML 2.0") {
12426:             return parent::split($string, $config, $context);
12427:         } else {
12428:             return preg_split('/\s+/', $string);
12429:         }
12430:     }
12431: 
12432:     12433: 12434: 12435: 12436: 12437: 
12438:     protected function filter($tokens, $config, $context)
12439:     {
12440:         $allowed = $config->get('Attr.AllowedClasses');
12441:         $forbidden = $config->get('Attr.ForbiddenClasses');
12442:         $ret = array();
12443:         foreach ($tokens as $token) {
12444:             if (($allowed === null || isset($allowed[$token])) &&
12445:                 !isset($forbidden[$token]) &&
12446:                 
12447:                 
12448:                 !in_array($token, $ret, true)
12449:             ) {
12450:                 $ret[] = $token;
12451:             }
12452:         }
12453:         return $ret;
12454:     }
12455: }
12456: 
12457: 
12458: 
12459: 12460: 12461: 
12462: class HTMLPurifier_AttrDef_HTML_Color extends HTMLPurifier_AttrDef
12463: {
12464: 
12465:     12466: 12467: 12468: 12469: 12470: 
12471:     public function validate($string, $config, $context)
12472:     {
12473:         static $colors = null;
12474:         if ($colors === null) {
12475:             $colors = $config->get('Core.ColorKeywords');
12476:         }
12477: 
12478:         $string = trim($string);
12479: 
12480:         if (empty($string)) {
12481:             return false;
12482:         }
12483:         $lower = strtolower($string);
12484:         if (isset($colors[$lower])) {
12485:             return $colors[$lower];
12486:         }
12487:         if ($string[0] === '#') {
12488:             $hex = substr($string, 1);
12489:         } else {
12490:             $hex = $string;
12491:         }
12492: 
12493:         $length = strlen($hex);
12494:         if ($length !== 3 && $length !== 6) {
12495:             return false;
12496:         }
12497:         if (!ctype_xdigit($hex)) {
12498:             return false;
12499:         }
12500:         if ($length === 3) {
12501:             $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
12502:         }
12503:         return "#$hex";
12504:     }
12505: }
12506: 
12507: 
12508: 
12509: 
12510: 
12511: 12512: 12513: 
12514: class HTMLPurifier_AttrDef_HTML_FrameTarget extends HTMLPurifier_AttrDef_Enum
12515: {
12516: 
12517:     12518: 12519: 
12520:     public $valid_values = false; 
12521: 
12522:     12523: 12524: 
12525:     protected $case_sensitive = false;
12526: 
12527:     public function __construct()
12528:     {
12529:     }
12530: 
12531:     12532: 12533: 12534: 12535: 12536: 
12537:     public function validate($string, $config, $context)
12538:     {
12539:         if ($this->valid_values === false) {
12540:             $this->valid_values = $config->get('Attr.AllowedFrameTargets');
12541:         }
12542:         return parent::validate($string, $config, $context);
12543:     }
12544: }
12545: 
12546: 
12547: 
12548: 
12549: 
12550: 12551: 12552: 12553: 12554: 12555: 12556: 12557: 
12558: 
12559: class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef
12560: {
12561: 
12562:     
12563:     
12564: 
12565:     12566: 12567: 12568: 12569: 
12570:     protected $selector;
12571: 
12572:     12573: 12574: 
12575:     public function __construct($selector = false)
12576:     {
12577:         $this->selector = $selector;
12578:     }
12579: 
12580:     12581: 12582: 12583: 12584: 12585: 
12586:     public function validate($id, $config, $context)
12587:     {
12588:         if (!$this->selector && !$config->get('Attr.EnableID')) {
12589:             return false;
12590:         }
12591: 
12592:         $id = trim($id); 
12593: 
12594:         if ($id === '') {
12595:             return false;
12596:         }
12597: 
12598:         $prefix = $config->get('Attr.IDPrefix');
12599:         if ($prefix !== '') {
12600:             $prefix .= $config->get('Attr.IDPrefixLocal');
12601:             
12602:             if (strpos($id, $prefix) !== 0) {
12603:                 $id = $prefix . $id;
12604:             }
12605:         } elseif ($config->get('Attr.IDPrefixLocal') !== '') {
12606:             trigger_error(
12607:                 '%Attr.IDPrefixLocal cannot be used unless ' .
12608:                 '%Attr.IDPrefix is set',
12609:                 E_USER_WARNING
12610:             );
12611:         }
12612: 
12613:         if (!$this->selector) {
12614:             $id_accumulator =& $context->get('IDAccumulator');
12615:             if (isset($id_accumulator->ids[$id])) {
12616:                 return false;
12617:             }
12618:         }
12619: 
12620:         
12621: 
12622:         if (ctype_alpha($id)) {
12623:             $result = true;
12624:         } else {
12625:             if (!ctype_alpha(@$id[0])) {
12626:                 return false;
12627:             }
12628:             
12629:             $trim = trim(
12630:                 $id,
12631:                 'A..Za..z0..9:-._'
12632:             );
12633:             $result = ($trim === '');
12634:         }
12635: 
12636:         $regexp = $config->get('Attr.IDBlacklistRegexp');
12637:         if ($regexp && preg_match($regexp, $id)) {
12638:             return false;
12639:         }
12640: 
12641:         if (!$this->selector && $result) {
12642:             $id_accumulator->add($id);
12643:         }
12644: 
12645:         
12646:         
12647:         
12648:         return $result ? $id : false;
12649:     }
12650: }
12651: 
12652: 
12653: 
12654: 
12655: 
12656: 12657: 12658: 
12659: class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef
12660: {
12661: 
12662:     12663: 12664: 
12665:     protected $max;
12666: 
12667:     12668: 12669: 
12670:     public function __construct($max = null)
12671:     {
12672:         $this->max = $max;
12673:     }
12674: 
12675:     12676: 12677: 12678: 12679: 12680: 
12681:     public function validate($string, $config, $context)
12682:     {
12683:         $string = trim($string);
12684:         if ($string === '0') {
12685:             return $string;
12686:         }
12687:         if ($string === '') {
12688:             return false;
12689:         }
12690:         $length = strlen($string);
12691:         if (substr($string, $length - 2) == 'px') {
12692:             $string = substr($string, 0, $length - 2);
12693:         }
12694:         if (!is_numeric($string)) {
12695:             return false;
12696:         }
12697:         $int = (int)$string;
12698: 
12699:         if ($int < 0) {
12700:             return '0';
12701:         }
12702: 
12703:         
12704:         
12705:         
12706: 
12707:         if ($this->max !== null && $int > $this->max) {
12708:             return (string)$this->max;
12709:         }
12710:         return (string)$int;
12711:     }
12712: 
12713:     12714: 12715: 12716: 
12717:     public function make($string)
12718:     {
12719:         if ($string === '') {
12720:             $max = null;
12721:         } else {
12722:             $max = (int)$string;
12723:         }
12724:         $class = get_class($this);
12725:         return new $class($max);
12726:     }
12727: }
12728: 
12729: 
12730: 
12731: 
12732: 
12733: 12734: 12735: 12736: 12737: 12738: 
12739: 
12740: class HTMLPurifier_AttrDef_HTML_Length extends HTMLPurifier_AttrDef_HTML_Pixels
12741: {
12742: 
12743:     12744: 12745: 12746: 12747: 12748: 
12749:     public function validate($string, $config, $context)
12750:     {
12751:         $string = trim($string);
12752:         if ($string === '') {
12753:             return false;
12754:         }
12755: 
12756:         $parent_result = parent::validate($string, $config, $context);
12757:         if ($parent_result !== false) {
12758:             return $parent_result;
12759:         }
12760: 
12761:         $length = strlen($string);
12762:         $last_char = $string[$length - 1];
12763: 
12764:         if ($last_char !== '%') {
12765:             return false;
12766:         }
12767: 
12768:         $points = substr($string, 0, $length - 1);
12769: 
12770:         if (!is_numeric($points)) {
12771:             return false;
12772:         }
12773: 
12774:         $points = (int)$points;
12775: 
12776:         if ($points < 0) {
12777:             return '0%';
12778:         }
12779:         if ($points > 100) {
12780:             return '100%';
12781:         }
12782:         return ((string)$points) . '%';
12783:     }
12784: }
12785: 
12786: 
12787: 
12788: 
12789: 
12790: 12791: 12792: 12793: 12794: 12795: 
12796: class HTMLPurifier_AttrDef_HTML_LinkTypes extends HTMLPurifier_AttrDef
12797: {
12798: 
12799:     12800: 12801: 12802: 
12803:     protected $name;
12804: 
12805:     12806: 12807: 
12808:     public function __construct($name)
12809:     {
12810:         $configLookup = array(
12811:             'rel' => 'AllowedRel',
12812:             'rev' => 'AllowedRev'
12813:         );
12814:         if (!isset($configLookup[$name])) {
12815:             trigger_error(
12816:                 'Unrecognized attribute name for link ' .
12817:                 'relationship.',
12818:                 E_USER_ERROR
12819:             );
12820:             return;
12821:         }
12822:         $this->name = $configLookup[$name];
12823:     }
12824: 
12825:     12826: 12827: 12828: 12829: 12830: 
12831:     public function validate($string, $config, $context)
12832:     {
12833:         $allowed = $config->get('Attr.' . $this->name);
12834:         if (empty($allowed)) {
12835:             return false;
12836:         }
12837: 
12838:         $string = $this->parseCDATA($string);
12839:         $parts = explode(' ', $string);
12840: 
12841:         
12842:         $ret_lookup = array();
12843:         foreach ($parts as $part) {
12844:             $part = strtolower(trim($part));
12845:             if (!isset($allowed[$part])) {
12846:                 continue;
12847:             }
12848:             $ret_lookup[$part] = true;
12849:         }
12850: 
12851:         if (empty($ret_lookup)) {
12852:             return false;
12853:         }
12854:         $string = implode(' ', array_keys($ret_lookup));
12855:         return $string;
12856:     }
12857: }
12858: 
12859: 
12860: 
12861: 
12862: 
12863: 12864: 12865: 12866: 12867: 12868: 
12869: class HTMLPurifier_AttrDef_HTML_MultiLength extends HTMLPurifier_AttrDef_HTML_Length
12870: {
12871: 
12872:     12873: 12874: 12875: 12876: 12877: 
12878:     public function validate($string, $config, $context)
12879:     {
12880:         $string = trim($string);
12881:         if ($string === '') {
12882:             return false;
12883:         }
12884: 
12885:         $parent_result = parent::validate($string, $config, $context);
12886:         if ($parent_result !== false) {
12887:             return $parent_result;
12888:         }
12889: 
12890:         $length = strlen($string);
12891:         $last_char = $string[$length - 1];
12892: 
12893:         if ($last_char !== '*') {
12894:             return false;
12895:         }
12896: 
12897:         $int = substr($string, 0, $length - 1);
12898: 
12899:         if ($int == '') {
12900:             return '*';
12901:         }
12902:         if (!is_numeric($int)) {
12903:             return false;
12904:         }
12905: 
12906:         $int = (int)$int;
12907:         if ($int < 0) {
12908:             return false;
12909:         }
12910:         if ($int == 0) {
12911:             return '0';
12912:         }
12913:         if ($int == 1) {
12914:             return '*';
12915:         }
12916:         return ((string)$int) . '*';
12917:     }
12918: }
12919: 
12920: 
12921: 
12922: 
12923: 
12924: abstract class HTMLPurifier_AttrDef_URI_Email extends HTMLPurifier_AttrDef
12925: {
12926: 
12927:     12928: 12929: 12930: 12931: 
12932:     public function unpack($string)
12933:     {
12934:         
12935:     }
12936: 
12937: }
12938: 
12939: 
12940: 
12941: 
12942: 
12943: 
12944: 
12945: 12946: 12947: 
12948: class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
12949: {
12950: 
12951:     12952: 12953: 12954: 
12955:     protected $ipv4;
12956: 
12957:     12958: 12959: 12960: 
12961:     protected $ipv6;
12962: 
12963:     public function __construct()
12964:     {
12965:         $this->ipv4 = new HTMLPurifier_AttrDef_URI_IPv4();
12966:         $this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6();
12967:     }
12968: 
12969:     12970: 12971: 12972: 12973: 12974: 
12975:     public function validate($string, $config, $context)
12976:     {
12977:         $length = strlen($string);
12978:         
12979:         
12980:         
12981:         
12982:         
12983:         
12984:         if ($string === '') {
12985:             return '';
12986:         }
12987:         if ($length > 1 && $string[0] === '[' && $string[$length - 1] === ']') {
12988:             
12989:             $ip = substr($string, 1, $length - 2);
12990:             $valid = $this->ipv6->validate($ip, $config, $context);
12991:             if ($valid === false) {
12992:                 return false;
12993:             }
12994:             return '[' . $valid . ']';
12995:         }
12996: 
12997:         
12998:         $ipv4 = $this->ipv4->validate($string, $config, $context);
12999:         if ($ipv4 !== false) {
13000:             return $ipv4;
13001:         }
13002: 
13003:         
13004: 
13005:         
13006:         
13007: 
13008:         
13009:         
13010:         
13011:         
13012:         
13013:         
13014:         
13015:         
13016:         
13017:         
13018:         
13019:         $underscore = $config->get('Core.AllowHostnameUnderscore') ? '_' : '';
13020: 
13021:         
13022:         $a   = '[a-z]';     
13023:         $an  = '[a-z0-9]';  
13024:         $and = "[a-z0-9-$underscore]"; 
13025:         
13026:         $domainlabel = "$an($and*$an)?";
13027:         
13028:         $toplabel = "$a($and*$an)?";
13029:         
13030:         if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
13031:             return $string;
13032:         }
13033: 
13034:         
13035:         
13036:         
13037: 
13038:         if ($config->get('Core.EnableIDNA')) {
13039:             $idna = new Net_IDNA2(array('encoding' => 'utf8', 'overlong' => false, 'strict' => true));
13040:             
13041:             $parts = explode('.', $string);
13042:             try {
13043:                 $new_parts = array();
13044:                 foreach ($parts as $part) {
13045:                     $encodable = false;
13046:                     for ($i = 0, $c = strlen($part); $i < $c; $i++) {
13047:                         if (ord($part[$i]) > 0x7a) {
13048:                             $encodable = true;
13049:                             break;
13050:                         }
13051:                     }
13052:                     if (!$encodable) {
13053:                         $new_parts[] = $part;
13054:                     } else {
13055:                         $new_parts[] = $idna->encode($part);
13056:                     }
13057:                 }
13058:                 $string = implode('.', $new_parts);
13059:                 if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
13060:                     return $string;
13061:                 }
13062:             } catch (Exception $e) {
13063:                 
13064:             }
13065:         }
13066:         return false;
13067:     }
13068: }
13069: 
13070: 
13071: 
13072: 
13073: 
13074: 13075: 13076: 13077: 
13078: class HTMLPurifier_AttrDef_URI_IPv4 extends HTMLPurifier_AttrDef
13079: {
13080: 
13081:     13082: 13083: 13084: 
13085:     protected $ip4;
13086: 
13087:     13088: 13089: 13090: 13091: 13092: 
13093:     public function validate($aIP, $config, $context)
13094:     {
13095:         if (!$this->ip4) {
13096:             $this->_loadRegex();
13097:         }
13098: 
13099:         if (preg_match('#^' . $this->ip4 . '$#s', $aIP)) {
13100:             return $aIP;
13101:         }
13102:         return false;
13103:     }
13104: 
13105:     13106: 13107: 13108: 
13109:     protected function _loadRegex()
13110:     {
13111:         $oct = '(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])'; 
13112:         $this->ip4 = "(?:{$oct}\\.{$oct}\\.{$oct}\\.{$oct})";
13113:     }
13114: }
13115: 
13116: 
13117: 
13118: 
13119: 
13120: 13121: 13122: 13123: 13124: 13125: 
13126: class HTMLPurifier_AttrDef_URI_IPv6 extends HTMLPurifier_AttrDef_URI_IPv4
13127: {
13128: 
13129:     13130: 13131: 13132: 13133: 13134: 
13135:     public function validate($aIP, $config, $context)
13136:     {
13137:         if (!$this->ip4) {
13138:             $this->_loadRegex();
13139:         }
13140: 
13141:         $original = $aIP;
13142: 
13143:         $hex = '[0-9a-fA-F]';
13144:         $blk = '(?:' . $hex . '{1,4})';
13145:         $pre = '(?:/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))'; 
13146: 
13147:         
13148:         if (strpos($aIP, '/') !== false) {
13149:             if (preg_match('#' . $pre . '$#s', $aIP, $find)) {
13150:                 $aIP = substr($aIP, 0, 0 - strlen($find[0]));
13151:                 unset($find);
13152:             } else {
13153:                 return false;
13154:             }
13155:         }
13156: 
13157:         
13158:         if (preg_match('#(?<=:' . ')' . $this->ip4 . '$#s', $aIP, $find)) {
13159:             $aIP = substr($aIP, 0, 0 - strlen($find[0]));
13160:             $ip = explode('.', $find[0]);
13161:             $ip = array_map('dechex', $ip);
13162:             $aIP .= $ip[0] . $ip[1] . ':' . $ip[2] . $ip[3];
13163:             unset($find, $ip);
13164:         }
13165: 
13166:         
13167:         $aIP = explode('::', $aIP);
13168:         $c = count($aIP);
13169:         if ($c > 2) {
13170:             return false;
13171:         } elseif ($c == 2) {
13172:             list($first, $second) = $aIP;
13173:             $first = explode(':', $first);
13174:             $second = explode(':', $second);
13175: 
13176:             if (count($first) + count($second) > 8) {
13177:                 return false;
13178:             }
13179: 
13180:             while (count($first) < 8) {
13181:                 array_push($first, '0');
13182:             }
13183: 
13184:             array_splice($first, 8 - count($second), 8, $second);
13185:             $aIP = $first;
13186:             unset($first, $second);
13187:         } else {
13188:             $aIP = explode(':', $aIP[0]);
13189:         }
13190:         $c = count($aIP);
13191: 
13192:         if ($c != 8) {
13193:             return false;
13194:         }
13195: 
13196:         
13197:         foreach ($aIP as $piece) {
13198:             if (!preg_match('#^[0-9a-fA-F]{4}$#s', sprintf('%04s', $piece))) {
13199:                 return false;
13200:             }
13201:         }
13202:         return $original;
13203:     }
13204: }
13205: 
13206: 
13207: 
13208: 
13209: 
13210: 13211: 13212: 13213: 
13214: class HTMLPurifier_AttrDef_URI_Email_SimpleCheck extends HTMLPurifier_AttrDef_URI_Email
13215: {
13216: 
13217:     13218: 13219: 13220: 13221: 13222: 
13223:     public function validate($string, $config, $context)
13224:     {
13225:         
13226:         
13227:         if ($string == '') {
13228:             return false;
13229:         }
13230:         $string = trim($string);
13231:         $result = preg_match('/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $string);
13232:         return $result ? $string : false;
13233:     }
13234: }
13235: 
13236: 
13237: 
13238: 
13239: 
13240: 13241: 13242: 
13243: class HTMLPurifier_AttrTransform_Background extends HTMLPurifier_AttrTransform
13244: {
13245:     13246: 13247: 13248: 13249: 13250: 
13251:     public function transform($attr, $config, $context)
13252:     {
13253:         if (!isset($attr['background'])) {
13254:             return $attr;
13255:         }
13256: 
13257:         $background = $this->confiscateAttr($attr, 'background');
13258:         
13259: 
13260:         $this->prependCSS($attr, "background-image:url($background);");
13261:         return $attr;
13262:     }
13263: }
13264: 
13265: 
13266: 
13267: 
13268: 
13269: 
13270: 
13271: 13272: 13273: 
13274: class HTMLPurifier_AttrTransform_BdoDir extends HTMLPurifier_AttrTransform
13275: {
13276: 
13277:     13278: 13279: 13280: 13281: 13282: 
13283:     public function transform($attr, $config, $context)
13284:     {
13285:         if (isset($attr['dir'])) {
13286:             return $attr;
13287:         }
13288:         $attr['dir'] = $config->get('Attr.DefaultTextDir');
13289:         return $attr;
13290:     }
13291: }
13292: 
13293: 
13294: 
13295: 
13296: 
13297: 13298: 13299: 
13300: class HTMLPurifier_AttrTransform_BgColor extends HTMLPurifier_AttrTransform
13301: {
13302:     13303: 13304: 13305: 13306: 13307: 
13308:     public function transform($attr, $config, $context)
13309:     {
13310:         if (!isset($attr['bgcolor'])) {
13311:             return $attr;
13312:         }
13313: 
13314:         $bgcolor = $this->confiscateAttr($attr, 'bgcolor');
13315:         
13316: 
13317:         $this->prependCSS($attr, "background-color:$bgcolor;");
13318:         return $attr;
13319:     }
13320: }
13321: 
13322: 
13323: 
13324: 
13325: 
13326: 13327: 13328: 
13329: class HTMLPurifier_AttrTransform_BoolToCSS extends HTMLPurifier_AttrTransform
13330: {
13331:     13332: 13333: 13334: 
13335:     protected $attr;
13336: 
13337:     13338: 13339: 13340: 
13341:     protected $css;
13342: 
13343:     13344: 13345: 13346: 
13347:     public function __construct($attr, $css)
13348:     {
13349:         $this->attr = $attr;
13350:         $this->css = $css;
13351:     }
13352: 
13353:     13354: 13355: 13356: 13357: 13358: 
13359:     public function transform($attr, $config, $context)
13360:     {
13361:         if (!isset($attr[$this->attr])) {
13362:             return $attr;
13363:         }
13364:         unset($attr[$this->attr]);
13365:         $this->prependCSS($attr, $this->css);
13366:         return $attr;
13367:     }
13368: }
13369: 
13370: 
13371: 
13372: 
13373: 
13374: 13375: 13376: 
13377: class HTMLPurifier_AttrTransform_Border extends HTMLPurifier_AttrTransform
13378: {
13379:     13380: 13381: 13382: 13383: 13384: 
13385:     public function transform($attr, $config, $context)
13386:     {
13387:         if (!isset($attr['border'])) {
13388:             return $attr;
13389:         }
13390:         $border_width = $this->confiscateAttr($attr, 'border');
13391:         
13392:         $this->prependCSS($attr, "border:{$border_width}px solid;");
13393:         return $attr;
13394:     }
13395: }
13396: 
13397: 
13398: 
13399: 
13400: 
13401: 13402: 13403: 13404: 
13405: class HTMLPurifier_AttrTransform_EnumToCSS extends HTMLPurifier_AttrTransform
13406: {
13407:     13408: 13409: 13410: 
13411:     protected $attr;
13412: 
13413:     13414: 13415: 13416: 
13417:     protected $enumToCSS = array();
13418: 
13419:     13420: 13421: 13422: 13423: 13424: 
13425:     protected $caseSensitive = false;
13426: 
13427:     13428: 13429: 13430: 13431: 
13432:     public function __construct($attr, $enum_to_css, $case_sensitive = false)
13433:     {
13434:         $this->attr = $attr;
13435:         $this->enumToCSS = $enum_to_css;
13436:         $this->caseSensitive = (bool)$case_sensitive;
13437:     }
13438: 
13439:     13440: 13441: 13442: 13443: 13444: 
13445:     public function transform($attr, $config, $context)
13446:     {
13447:         if (!isset($attr[$this->attr])) {
13448:             return $attr;
13449:         }
13450: 
13451:         $value = trim($attr[$this->attr]);
13452:         unset($attr[$this->attr]);
13453: 
13454:         if (!$this->caseSensitive) {
13455:             $value = strtolower($value);
13456:         }
13457: 
13458:         if (!isset($this->enumToCSS[$value])) {
13459:             return $attr;
13460:         }
13461:         $this->prependCSS($attr, $this->enumToCSS[$value]);
13462:         return $attr;
13463:     }
13464: }
13465: 
13466: 
13467: 
13468: 
13469: 
13470: 
13471: 
13472: 13473: 13474: 13475: 13476: 13477: 
13478: class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform
13479: {
13480: 
13481:     13482: 13483: 13484: 13485: 13486: 
13487:     public function transform($attr, $config, $context)
13488:     {
13489:         $src = true;
13490:         if (!isset($attr['src'])) {
13491:             if ($config->get('Core.RemoveInvalidImg')) {
13492:                 return $attr;
13493:             }
13494:             $attr['src'] = $config->get('Attr.DefaultInvalidImage');
13495:             $src = false;
13496:         }
13497: 
13498:         if (!isset($attr['alt'])) {
13499:             if ($src) {
13500:                 $alt = $config->get('Attr.DefaultImageAlt');
13501:                 if ($alt === null) {
13502:                     
13503:                     $attr['alt'] = substr(basename($attr['src']), 0, 40);
13504:                 } else {
13505:                     $attr['alt'] = $alt;
13506:                 }
13507:             } else {
13508:                 $attr['alt'] = $config->get('Attr.DefaultInvalidImageAlt');
13509:             }
13510:         }
13511:         return $attr;
13512:     }
13513: }
13514: 
13515: 
13516: 
13517: 
13518: 
13519: 13520: 13521: 
13522: class HTMLPurifier_AttrTransform_ImgSpace extends HTMLPurifier_AttrTransform
13523: {
13524:     13525: 13526: 
13527:     protected $attr;
13528: 
13529:     13530: 13531: 
13532:     protected $css = array(
13533:         'hspace' => array('left', 'right'),
13534:         'vspace' => array('top', 'bottom')
13535:     );
13536: 
13537:     13538: 13539: 
13540:     public function __construct($attr)
13541:     {
13542:         $this->attr = $attr;
13543:         if (!isset($this->css[$attr])) {
13544:             trigger_error(htmlspecialchars($attr) . ' is not valid space attribute');
13545:         }
13546:     }
13547: 
13548:     13549: 13550: 13551: 13552: 13553: 
13554:     public function transform($attr, $config, $context)
13555:     {
13556:         if (!isset($attr[$this->attr])) {
13557:             return $attr;
13558:         }
13559: 
13560:         $width = $this->confiscateAttr($attr, $this->attr);
13561:         
13562: 
13563:         if (!isset($this->css[$this->attr])) {
13564:             return $attr;
13565:         }
13566: 
13567:         $style = '';
13568:         foreach ($this->css[$this->attr] as $suffix) {
13569:             $property = "margin-$suffix";
13570:             $style .= "$property:{$width}px;";
13571:         }
13572:         $this->prependCSS($attr, $style);
13573:         return $attr;
13574:     }
13575: }
13576: 
13577: 
13578: 
13579: 
13580: 
13581: 13582: 13583: 13584: 
13585: class HTMLPurifier_AttrTransform_Input extends HTMLPurifier_AttrTransform
13586: {
13587:     13588: 13589: 
13590:     protected $pixels;
13591: 
13592:     public function __construct()
13593:     {
13594:         $this->pixels = new HTMLPurifier_AttrDef_HTML_Pixels();
13595:     }
13596: 
13597:     13598: 13599: 13600: 13601: 13602: 
13603:     public function transform($attr, $config, $context)
13604:     {
13605:         if (!isset($attr['type'])) {
13606:             $t = 'text';
13607:         } else {
13608:             $t = strtolower($attr['type']);
13609:         }
13610:         if (isset($attr['checked']) && $t !== 'radio' && $t !== 'checkbox') {
13611:             unset($attr['checked']);
13612:         }
13613:         if (isset($attr['maxlength']) && $t !== 'text' && $t !== 'password') {
13614:             unset($attr['maxlength']);
13615:         }
13616:         if (isset($attr['size']) && $t !== 'text' && $t !== 'password') {
13617:             $result = $this->pixels->validate($attr['size'], $config, $context);
13618:             if ($result === false) {
13619:                 unset($attr['size']);
13620:             } else {
13621:                 $attr['size'] = $result;
13622:             }
13623:         }
13624:         if (isset($attr['src']) && $t !== 'image') {
13625:             unset($attr['src']);
13626:         }
13627:         if (!isset($attr['value']) && ($t === 'radio' || $t === 'checkbox')) {
13628:             $attr['value'] = '';
13629:         }
13630:         return $attr;
13631:     }
13632: }
13633: 
13634: 
13635: 
13636: 
13637: 
13638: 13639: 13640: 13641: 13642: 
13643: class HTMLPurifier_AttrTransform_Lang extends HTMLPurifier_AttrTransform
13644: {
13645: 
13646:     13647: 13648: 13649: 13650: 13651: 
13652:     public function transform($attr, $config, $context)
13653:     {
13654:         $lang = isset($attr['lang']) ? $attr['lang'] : false;
13655:         $xml_lang = isset($attr['xml:lang']) ? $attr['xml:lang'] : false;
13656: 
13657:         if ($lang !== false && $xml_lang === false) {
13658:             $attr['xml:lang'] = $lang;
13659:         } elseif ($xml_lang !== false) {
13660:             $attr['lang'] = $xml_lang;
13661:         }
13662:         return $attr;
13663:     }
13664: }
13665: 
13666: 
13667: 
13668: 
13669: 
13670: 13671: 13672: 
13673: class HTMLPurifier_AttrTransform_Length extends HTMLPurifier_AttrTransform
13674: {
13675: 
13676:     13677: 13678: 
13679:     protected $name;
13680: 
13681:     13682: 13683: 
13684:     protected $cssName;
13685: 
13686:     public function __construct($name, $css_name = null)
13687:     {
13688:         $this->name = $name;
13689:         $this->cssName = $css_name ? $css_name : $name;
13690:     }
13691: 
13692:     13693: 13694: 13695: 13696: 13697: 
13698:     public function transform($attr, $config, $context)
13699:     {
13700:         if (!isset($attr[$this->name])) {
13701:             return $attr;
13702:         }
13703:         $length = $this->confiscateAttr($attr, $this->name);
13704:         if (ctype_digit($length)) {
13705:             $length .= 'px';
13706:         }
13707:         $this->prependCSS($attr, $this->cssName . ":$length;");
13708:         return $attr;
13709:     }
13710: }
13711: 
13712: 
13713: 
13714: 
13715: 
13716: 13717: 13718: 
13719: class HTMLPurifier_AttrTransform_Name extends HTMLPurifier_AttrTransform
13720: {
13721: 
13722:     13723: 13724: 13725: 13726: 13727: 
13728:     public function transform($attr, $config, $context)
13729:     {
13730:         
13731:         if ($config->get('HTML.Attr.Name.UseCDATA')) {
13732:             return $attr;
13733:         }
13734:         if (!isset($attr['name'])) {
13735:             return $attr;
13736:         }
13737:         $id = $this->confiscateAttr($attr, 'name');
13738:         if (isset($attr['id'])) {
13739:             return $attr;
13740:         }
13741:         $attr['id'] = $id;
13742:         return $attr;
13743:     }
13744: }
13745: 
13746: 
13747: 
13748: 
13749: 
13750: 13751: 13752: 13753: 13754: 
13755: class HTMLPurifier_AttrTransform_NameSync extends HTMLPurifier_AttrTransform
13756: {
13757: 
13758:     public function __construct()
13759:     {
13760:         $this->idDef = new HTMLPurifier_AttrDef_HTML_ID();
13761:     }
13762: 
13763:     13764: 13765: 13766: 13767: 13768: 
13769:     public function transform($attr, $config, $context)
13770:     {
13771:         if (!isset($attr['name'])) {
13772:             return $attr;
13773:         }
13774:         $name = $attr['name'];
13775:         if (isset($attr['id']) && $attr['id'] === $name) {
13776:             return $attr;
13777:         }
13778:         $result = $this->idDef->validate($name, $config, $context);
13779:         if ($result === false) {
13780:             unset($attr['name']);
13781:         } else {
13782:             $attr['name'] = $result;
13783:         }
13784:         return $attr;
13785:     }
13786: }
13787: 
13788: 
13789: 
13790: 
13791: 
13792: 
13793: 
13794: 13795: 13796: 13797: 
13798: class HTMLPurifier_AttrTransform_Nofollow extends HTMLPurifier_AttrTransform
13799: {
13800:     13801: 13802: 
13803:     private $parser;
13804: 
13805:     public function __construct()
13806:     {
13807:         $this->parser = new HTMLPurifier_URIParser();
13808:     }
13809: 
13810:     13811: 13812: 13813: 13814: 13815: 
13816:     public function transform($attr, $config, $context)
13817:     {
13818:         if (!isset($attr['href'])) {
13819:             return $attr;
13820:         }
13821: 
13822:         
13823:         $url = $this->parser->parse($attr['href']);
13824:         $scheme = $url->getSchemeObj($config, $context);
13825: 
13826:         if ($scheme->browsable && !$url->isLocal($config, $context)) {
13827:             if (isset($attr['rel'])) {
13828:                 $rels = explode(' ', $attr['rel']);
13829:                 if (!in_array('nofollow', $rels)) {
13830:                     $rels[] = 'nofollow';
13831:                 }
13832:                 $attr['rel'] = implode(' ', $rels);
13833:             } else {
13834:                 $attr['rel'] = 'nofollow';
13835:             }
13836:         }
13837:         return $attr;
13838:     }
13839: }
13840: 
13841: 
13842: 
13843: 
13844: 
13845: class HTMLPurifier_AttrTransform_SafeEmbed extends HTMLPurifier_AttrTransform
13846: {
13847:     13848: 13849: 
13850:     public $name = "SafeEmbed";
13851: 
13852:     13853: 13854: 13855: 13856: 13857: 
13858:     public function transform($attr, $config, $context)
13859:     {
13860:         $attr['allowscriptaccess'] = 'never';
13861:         $attr['allownetworking'] = 'internal';
13862:         $attr['type'] = 'application/x-shockwave-flash';
13863:         return $attr;
13864:     }
13865: }
13866: 
13867: 
13868: 
13869: 
13870: 
13871: 13872: 13873: 
13874: class HTMLPurifier_AttrTransform_SafeObject extends HTMLPurifier_AttrTransform
13875: {
13876:     13877: 13878: 
13879:     public $name = "SafeObject";
13880: 
13881:     13882: 13883: 13884: 13885: 13886: 
13887:     public function transform($attr, $config, $context)
13888:     {
13889:         if (!isset($attr['type'])) {
13890:             $attr['type'] = 'application/x-shockwave-flash';
13891:         }
13892:         return $attr;
13893:     }
13894: }
13895: 
13896: 
13897: 
13898: 
13899: 
13900: 13901: 13902: 13903: 13904: 13905: 13906: 13907: 13908: 13909: 13910: 13911: 
13912: class HTMLPurifier_AttrTransform_SafeParam extends HTMLPurifier_AttrTransform
13913: {
13914:     13915: 13916: 
13917:     public $name = "SafeParam";
13918: 
13919:     13920: 13921: 
13922:     private $uri;
13923: 
13924:     public function __construct()
13925:     {
13926:         $this->uri = new HTMLPurifier_AttrDef_URI(true); 
13927:         $this->wmode = new HTMLPurifier_AttrDef_Enum(array('window', 'opaque', 'transparent'));
13928:     }
13929: 
13930:     13931: 13932: 13933: 13934: 13935: 
13936:     public function transform($attr, $config, $context)
13937:     {
13938:         
13939:         
13940:         switch ($attr['name']) {
13941:             
13942:             
13943:             case 'allowScriptAccess':
13944:                 $attr['value'] = 'never';
13945:                 break;
13946:             case 'allowNetworking':
13947:                 $attr['value'] = 'internal';
13948:                 break;
13949:             case 'allowFullScreen':
13950:                 if ($config->get('HTML.FlashAllowFullScreen')) {
13951:                     $attr['value'] = ($attr['value'] == 'true') ? 'true' : 'false';
13952:                 } else {
13953:                     $attr['value'] = 'false';
13954:                 }
13955:                 break;
13956:             case 'wmode':
13957:                 $attr['value'] = $this->wmode->validate($attr['value'], $config, $context);
13958:                 break;
13959:             case 'movie':
13960:             case 'src':
13961:                 $attr['name'] = "movie";
13962:                 $attr['value'] = $this->uri->validate($attr['value'], $config, $context);
13963:                 break;
13964:             case 'flashvars':
13965:                 
13966:                 
13967:                 break;
13968:             
13969:             default:
13970:                 $attr['name'] = $attr['value'] = null;
13971:         }
13972:         return $attr;
13973:     }
13974: }
13975: 
13976: 
13977: 
13978: 
13979: 
13980: 13981: 13982: 
13983: class HTMLPurifier_AttrTransform_ScriptRequired extends HTMLPurifier_AttrTransform
13984: {
13985:     13986: 13987: 13988: 13989: 13990: 
13991:     public function transform($attr, $config, $context)
13992:     {
13993:         if (!isset($attr['type'])) {
13994:             $attr['type'] = 'text/javascript';
13995:         }
13996:         return $attr;
13997:     }
13998: }
13999: 
14000: 
14001: 
14002: 
14003: 
14004: 
14005: 
14006: 14007: 14008: 14009: 14010: 
14011: class HTMLPurifier_AttrTransform_TargetBlank extends HTMLPurifier_AttrTransform
14012: {
14013:     14014: 14015: 
14016:     private $parser;
14017: 
14018:     public function __construct()
14019:     {
14020:         $this->parser = new HTMLPurifier_URIParser();
14021:     }
14022: 
14023:     14024: 14025: 14026: 14027: 14028: 
14029:     public function transform($attr, $config, $context)
14030:     {
14031:         if (!isset($attr['href'])) {
14032:             return $attr;
14033:         }
14034: 
14035:         
14036:         $url = $this->parser->parse($attr['href']);
14037:         $scheme = $url->getSchemeObj($config, $context);
14038: 
14039:         if ($scheme->browsable && !$url->isBenign($config, $context)) {
14040:             $attr['target'] = '_blank';
14041:         }
14042:         return $attr;
14043:     }
14044: }
14045: 
14046: 
14047: 
14048: 
14049: 
14050: 14051: 14052: 
14053: class HTMLPurifier_AttrTransform_Textarea extends HTMLPurifier_AttrTransform
14054: {
14055:     14056: 14057: 14058: 14059: 14060: 
14061:     public function transform($attr, $config, $context)
14062:     {
14063:         
14064:         if (!isset($attr['cols'])) {
14065:             $attr['cols'] = '22';
14066:         }
14067:         if (!isset($attr['rows'])) {
14068:             $attr['rows'] = '3';
14069:         }
14070:         return $attr;
14071:     }
14072: }
14073: 
14074: 
14075: 
14076: 
14077: 
14078: 14079: 14080: 14081: 14082: 14083: 14084: 14085: 14086: 
14087: class HTMLPurifier_ChildDef_Chameleon extends HTMLPurifier_ChildDef
14088: {
14089: 
14090:     14091: 14092: 14093: 
14094:     public $inline;
14095: 
14096:     14097: 14098: 14099: 
14100:     public $block;
14101: 
14102:     14103: 14104: 
14105:     public $type = 'chameleon';
14106: 
14107:     14108: 14109: 14110: 
14111:     public function __construct($inline, $block)
14112:     {
14113:         $this->inline = new HTMLPurifier_ChildDef_Optional($inline);
14114:         $this->block = new HTMLPurifier_ChildDef_Optional($block);
14115:         $this->elements = $this->block->elements;
14116:     }
14117: 
14118:     14119: 14120: 14121: 14122: 14123: 
14124:     public function validateChildren($children, $config, $context)
14125:     {
14126:         if ($context->get('IsInline') === false) {
14127:             return $this->block->validateChildren(
14128:                 $children,
14129:                 $config,
14130:                 $context
14131:             );
14132:         } else {
14133:             return $this->inline->validateChildren(
14134:                 $children,
14135:                 $config,
14136:                 $context
14137:             );
14138:         }
14139:     }
14140: }
14141: 
14142: 
14143: 
14144: 
14145: 
14146: 14147: 14148: 14149: 14150: 14151: 
14152: class HTMLPurifier_ChildDef_Custom extends HTMLPurifier_ChildDef
14153: {
14154:     14155: 14156: 
14157:     public $type = 'custom';
14158: 
14159:     14160: 14161: 
14162:     public $allow_empty = false;
14163: 
14164:     14165: 14166: 14167: 
14168:     public $dtd_regex;
14169: 
14170:     14171: 14172: 14173: 
14174:     private $_pcre_regex;
14175: 
14176:     14177: 14178: 
14179:     public function __construct($dtd_regex)
14180:     {
14181:         $this->dtd_regex = $dtd_regex;
14182:         $this->_compileRegex();
14183:     }
14184: 
14185:     14186: 14187: 
14188:     protected function _compileRegex()
14189:     {
14190:         $raw = str_replace(' ', '', $this->dtd_regex);
14191:         if ($raw{0} != '(') {
14192:             $raw = "($raw)";
14193:         }
14194:         $el = '[#a-zA-Z0-9_.-]+';
14195:         $reg = $raw;
14196: 
14197:         
14198:         
14199: 
14200:         
14201:         preg_match_all("/$el/", $reg, $matches);
14202:         foreach ($matches[0] as $match) {
14203:             $this->elements[$match] = true;
14204:         }
14205: 
14206:         
14207:         $reg = preg_replace("/$el/", '(,\\0)', $reg);
14208: 
14209:         
14210:         $reg = preg_replace("/([^,(|]\(+),/", '\\1', $reg);
14211: 
14212:         
14213:         $reg = preg_replace("/,\(/", '(', $reg);
14214: 
14215:         $this->_pcre_regex = $reg;
14216:     }
14217: 
14218:     14219: 14220: 14221: 14222: 14223: 
14224:     public function validateChildren($children, $config, $context)
14225:     {
14226:         $list_of_children = '';
14227:         $nesting = 0; 
14228:         foreach ($children as $node) {
14229:             if (!empty($node->is_whitespace)) {
14230:                 continue;
14231:             }
14232:             $list_of_children .= $node->name . ',';
14233:         }
14234:         
14235:         $list_of_children = ',' . rtrim($list_of_children, ',');
14236:         $okay =
14237:             preg_match(
14238:                 '/^,?' . $this->_pcre_regex . '$/',
14239:                 $list_of_children
14240:             );
14241:         return (bool)$okay;
14242:     }
14243: }
14244: 
14245: 
14246: 
14247: 
14248: 
14249: 14250: 14251: 14252: 14253: 14254: 14255: 
14256: class HTMLPurifier_ChildDef_Empty extends HTMLPurifier_ChildDef
14257: {
14258:     14259: 14260: 
14261:     public $allow_empty = true;
14262: 
14263:     14264: 14265: 
14266:     public $type = 'empty';
14267: 
14268:     public function __construct()
14269:     {
14270:     }
14271: 
14272:     14273: 14274: 14275: 14276: 14277: 
14278:     public function validateChildren($children, $config, $context)
14279:     {
14280:         return array();
14281:     }
14282: }
14283: 
14284: 
14285: 
14286: 
14287: 
14288: 14289: 14290: 14291: 14292: 14293: 14294: 14295: 14296: 
14297: class HTMLPurifier_ChildDef_List extends HTMLPurifier_ChildDef
14298: {
14299:     14300: 14301: 
14302:     public $type = 'list';
14303:     14304: 14305: 
14306:     
14307:     
14308:     public $elements = array('li' => true, 'ul' => true, 'ol' => true);
14309: 
14310:     14311: 14312: 14313: 14314: 14315: 
14316:     public function validateChildren($children, $config, $context)
14317:     {
14318:         
14319:         $this->whitespace = false;
14320: 
14321:         
14322:         if (empty($children)) {
14323:             return false;
14324:         }
14325: 
14326:         
14327:         $result = array();
14328: 
14329:         
14330:         $all_whitespace = true;
14331: 
14332:         $current_li = false;
14333: 
14334:         foreach ($children as $node) {
14335:             if (!empty($node->is_whitespace)) {
14336:                 $result[] = $node;
14337:                 continue;
14338:             }
14339:             $all_whitespace = false; 
14340: 
14341:             if ($node->name === 'li') {
14342:                 
14343:                 $current_li = $node;
14344:                 $result[] = $node;
14345:             } else {
14346:                 
14347:                 
14348:                 
14349:                 
14350:                 
14351:                 
14352:                 
14353:                 if ($current_li === false) {
14354:                     $current_li = new HTMLPurifier_Node_Element('li');
14355:                     $result[] = $current_li;
14356:                 }
14357:                 $current_li->children[] = $node;
14358:                 $current_li->empty = false; 
14359:             }
14360:         }
14361:         if (empty($result)) {
14362:             return false;
14363:         }
14364:         if ($all_whitespace) {
14365:             return false;
14366:         }
14367:         return $result;
14368:     }
14369: }
14370: 
14371: 
14372: 
14373: 
14374: 
14375: 14376: 14377: 
14378: class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef
14379: {
14380:     14381: 14382: 14383: 
14384:     public $elements = array();
14385: 
14386:     14387: 14388: 14389: 
14390:     protected $whitespace = false;
14391: 
14392:     14393: 14394: 
14395:     public function __construct($elements)
14396:     {
14397:         if (is_string($elements)) {
14398:             $elements = str_replace(' ', '', $elements);
14399:             $elements = explode('|', $elements);
14400:         }
14401:         $keys = array_keys($elements);
14402:         if ($keys == array_keys($keys)) {
14403:             $elements = array_flip($elements);
14404:             foreach ($elements as $i => $x) {
14405:                 $elements[$i] = true;
14406:                 if (empty($i)) {
14407:                     unset($elements[$i]);
14408:                 } 
14409:             }
14410:         }
14411:         $this->elements = $elements;
14412:     }
14413: 
14414:     14415: 14416: 
14417:     public $allow_empty = false;
14418: 
14419:     14420: 14421: 
14422:     public $type = 'required';
14423: 
14424:     14425: 14426: 14427: 14428: 14429: 
14430:     public function validateChildren($children, $config, $context)
14431:     {
14432:         
14433:         $this->whitespace = false;
14434: 
14435:         
14436:         if (empty($children)) {
14437:             return false;
14438:         }
14439: 
14440:         
14441:         $result = array();
14442: 
14443:         
14444:         
14445:         
14446:         $pcdata_allowed = isset($this->elements['#PCDATA']);
14447: 
14448:         
14449:         $all_whitespace = true;
14450: 
14451:         $stack = array_reverse($children);
14452:         while (!empty($stack)) {
14453:             $node = array_pop($stack);
14454:             if (!empty($node->is_whitespace)) {
14455:                 $result[] = $node;
14456:                 continue;
14457:             }
14458:             $all_whitespace = false; 
14459: 
14460:             if (!isset($this->elements[$node->name])) {
14461:                 
14462:                 
14463:                 if ($pcdata_allowed && $node instanceof HTMLPurifier_Node_Text) {
14464:                     $result[] = $node;
14465:                     continue;
14466:                 }
14467:                 
14468:                 
14469:                 if ($node instanceof HTMLPurifier_Node_Element) {
14470:                     for ($i = count($node->children) - 1; $i >= 0; $i--) {
14471:                         $stack[] = $node->children[$i];
14472:                     }
14473:                     continue;
14474:                 }
14475:                 continue;
14476:             }
14477:             $result[] = $node;
14478:         }
14479:         if (empty($result)) {
14480:             return false;
14481:         }
14482:         if ($all_whitespace) {
14483:             $this->whitespace = true;
14484:             return false;
14485:         }
14486:         return $result;
14487:     }
14488: }
14489: 
14490: 
14491: 
14492: 
14493: 
14494: 14495: 14496: 14497: 14498: 14499: 14500: 
14501: class HTMLPurifier_ChildDef_Optional extends HTMLPurifier_ChildDef_Required
14502: {
14503:     14504: 14505: 
14506:     public $allow_empty = true;
14507: 
14508:     14509: 14510: 
14511:     public $type = 'optional';
14512: 
14513:     14514: 14515: 14516: 14517: 14518: 
14519:     public function validateChildren($children, $config, $context)
14520:     {
14521:         $result = parent::validateChildren($children, $config, $context);
14522:         
14523:         if ($result === false) {
14524:             if (empty($children)) {
14525:                 return true;
14526:             } elseif ($this->whitespace) {
14527:                 return $children;
14528:             } else {
14529:                 return array();
14530:             }
14531:         }
14532:         return $result;
14533:     }
14534: }
14535: 
14536: 
14537: 
14538: 
14539: 
14540: 14541: 14542: 
14543: class HTMLPurifier_ChildDef_StrictBlockquote extends HTMLPurifier_ChildDef_Required
14544: {
14545:     14546: 14547: 
14548:     protected $real_elements;
14549: 
14550:     14551: 14552: 
14553:     protected $fake_elements;
14554: 
14555:     14556: 14557: 
14558:     public $allow_empty = true;
14559: 
14560:     14561: 14562: 
14563:     public $type = 'strictblockquote';
14564: 
14565:     14566: 14567: 
14568:     protected $init = false;
14569: 
14570:     14571: 14572: 14573: 14574: 14575: 
14576:     public function getAllowedElements($config)
14577:     {
14578:         $this->init($config);
14579:         return $this->fake_elements;
14580:     }
14581: 
14582:     14583: 14584: 14585: 14586: 14587: 
14588:     public function validateChildren($children, $config, $context)
14589:     {
14590:         $this->init($config);
14591: 
14592:         
14593:         $this->elements = $this->fake_elements;
14594:         $result = parent::validateChildren($children, $config, $context);
14595:         $this->elements = $this->real_elements;
14596: 
14597:         if ($result === false) {
14598:             return array();
14599:         }
14600:         if ($result === true) {
14601:             $result = $children;
14602:         }
14603: 
14604:         $def = $config->getHTMLDefinition();
14605:         $block_wrap_name = $def->info_block_wrapper;
14606:         $block_wrap = false;
14607:         $ret = array();
14608: 
14609:         foreach ($result as $node) {
14610:             if ($block_wrap === false) {
14611:                 if (($node instanceof HTMLPurifier_Node_Text && !$node->is_whitespace) ||
14612:                     ($node instanceof HTMLPurifier_Node_Element && !isset($this->elements[$node->name]))) {
14613:                         $block_wrap = new HTMLPurifier_Node_Element($def->info_block_wrapper);
14614:                         $ret[] = $block_wrap;
14615:                 }
14616:             } else {
14617:                 if ($node instanceof HTMLPurifier_Node_Element && isset($this->elements[$node->name])) {
14618:                     $block_wrap = false;
14619: 
14620:                 }
14621:             }
14622:             if ($block_wrap) {
14623:                 $block_wrap->children[] = $node;
14624:             } else {
14625:                 $ret[] = $node;
14626:             }
14627:         }
14628:         return $ret;
14629:     }
14630: 
14631:     14632: 14633: 
14634:     private function init($config)
14635:     {
14636:         if (!$this->init) {
14637:             $def = $config->getHTMLDefinition();
14638:             
14639:             $this->real_elements = $this->elements;
14640:             $this->fake_elements = $def->info_content_sets['Flow'];
14641:             $this->fake_elements['#PCDATA'] = true;
14642:             $this->init = true;
14643:         }
14644:     }
14645: }
14646: 
14647: 
14648: 
14649: 
14650: 
14651: 14652: 14653: 14654: 14655: 14656: 14657: 14658: 14659: 14660: 14661: 14662: 14663: 14664: 14665: 14666: 14667: 14668: 14669: 14670: 14671: 14672: 14673: 14674: 14675: 14676: 14677: 14678: 14679: 
14680: class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef
14681: {
14682:     14683: 14684: 
14685:     public $allow_empty = false;
14686: 
14687:     14688: 14689: 
14690:     public $type = 'table';
14691: 
14692:     14693: 14694: 
14695:     public $elements = array(
14696:         'tr' => true,
14697:         'tbody' => true,
14698:         'thead' => true,
14699:         'tfoot' => true,
14700:         'caption' => true,
14701:         'colgroup' => true,
14702:         'col' => true
14703:     );
14704: 
14705:     public function __construct()
14706:     {
14707:     }
14708: 
14709:     14710: 14711: 14712: 14713: 14714: 
14715:     public function validateChildren($children, $config, $context)
14716:     {
14717:         if (empty($children)) {
14718:             return false;
14719:         }
14720: 
14721:         
14722:         $caption = false;
14723:         $thead = false;
14724:         $tfoot = false;
14725: 
14726:         
14727:         $initial_ws = array();
14728:         $after_caption_ws = array();
14729:         $after_thead_ws = array();
14730:         $after_tfoot_ws = array();
14731: 
14732:         
14733:         $cols = array();
14734:         $content = array();
14735: 
14736:         $tbody_mode = false; 
14737:                              
14738: 
14739:         $ws_accum =& $initial_ws;
14740: 
14741:         foreach ($children as $node) {
14742:             if ($node instanceof HTMLPurifier_Node_Comment) {
14743:                 $ws_accum[] = $node;
14744:                 continue;
14745:             }
14746:             switch ($node->name) {
14747:             case 'tbody':
14748:                 $tbody_mode = true;
14749:                 
14750:             case 'tr':
14751:                 $content[] = $node;
14752:                 $ws_accum =& $content;
14753:                 break;
14754:             case 'caption':
14755:                 
14756:                 if ($caption !== false)  break;
14757:                 $caption = $node;
14758:                 $ws_accum =& $after_caption_ws;
14759:                 break;
14760:             case 'thead':
14761:                 $tbody_mode = true;
14762:                 
14763:                 
14764:                 
14765:                 
14766:                 
14767:                 
14768:                 if ($thead === false) {
14769:                     $thead = $node;
14770:                     $ws_accum =& $after_thead_ws;
14771:                 } else {
14772:                     
14773:                     
14774:                     
14775:                     
14776:                     
14777:                     
14778:                     
14779:                     
14780:                     
14781:                     $node->name = 'tbody';
14782:                     $content[] = $node;
14783:                     $ws_accum =& $content;
14784:                 }
14785:                 break;
14786:             case 'tfoot':
14787:                 
14788:                 $tbody_mode = true;
14789:                 if ($tfoot === false) {
14790:                     $tfoot = $node;
14791:                     $ws_accum =& $after_tfoot_ws;
14792:                 } else {
14793:                     $node->name = 'tbody';
14794:                     $content[] = $node;
14795:                     $ws_accum =& $content;
14796:                 }
14797:                 break;
14798:             case 'colgroup':
14799:             case 'col':
14800:                 $cols[] = $node;
14801:                 $ws_accum =& $cols;
14802:                 break;
14803:             case '#PCDATA':
14804:                 
14805:                 
14806:                 
14807:                 
14808:                 if (!empty($node->is_whitespace)) {
14809:                     $ws_accum[] = $node;
14810:                 }
14811:                 break;
14812:             }
14813:         }
14814: 
14815:         if (empty($content)) {
14816:             return false;
14817:         }
14818: 
14819:         $ret = $initial_ws;
14820:         if ($caption !== false) {
14821:             $ret[] = $caption;
14822:             $ret = array_merge($ret, $after_caption_ws);
14823:         }
14824:         if ($cols !== false) {
14825:             $ret = array_merge($ret, $cols);
14826:         }
14827:         if ($thead !== false) {
14828:             $ret[] = $thead;
14829:             $ret = array_merge($ret, $after_thead_ws);
14830:         }
14831:         if ($tfoot !== false) {
14832:             $ret[] = $tfoot;
14833:             $ret = array_merge($ret, $after_tfoot_ws);
14834:         }
14835: 
14836:         if ($tbody_mode) {
14837:             
14838:             $current_tr_tbody = null;
14839: 
14840:             foreach($content as $node) {
14841:                 switch ($node->name) {
14842:                 case 'tbody':
14843:                     $current_tr_tbody = null;
14844:                     $ret[] = $node;
14845:                     break;
14846:                 case 'tr':
14847:                     if ($current_tr_tbody === null) {
14848:                         $current_tr_tbody = new HTMLPurifier_Node_Element('tbody');
14849:                         $ret[] = $current_tr_tbody;
14850:                     }
14851:                     $current_tr_tbody->children[] = $node;
14852:                     break;
14853:                 case '#PCDATA':
14854:                     assert($node->is_whitespace);
14855:                     if ($current_tr_tbody === null) {
14856:                         $ret[] = $node;
14857:                     } else {
14858:                         $current_tr_tbody->children[] = $node;
14859:                     }
14860:                     break;
14861:                 }
14862:             }
14863:         } else {
14864:             $ret = array_merge($ret, $content);
14865:         }
14866: 
14867:         return $ret;
14868: 
14869:     }
14870: }
14871: 
14872: 
14873: 
14874: 
14875: 
14876: class HTMLPurifier_DefinitionCache_Decorator extends HTMLPurifier_DefinitionCache
14877: {
14878: 
14879:     14880: 14881: 14882: 
14883:     public $cache;
14884: 
14885:     14886: 14887: 14888: 
14889:     public $name;
14890: 
14891:     public function __construct()
14892:     {
14893:     }
14894: 
14895:     14896: 14897: 14898: 14899: 
14900:     public function decorate(&$cache)
14901:     {
14902:         $decorator = $this->copy();
14903:         
14904:         $decorator->cache =& $cache;
14905:         $decorator->type = $cache->type;
14906:         return $decorator;
14907:     }
14908: 
14909:     14910: 14911: 14912: 
14913:     public function copy()
14914:     {
14915:         return new HTMLPurifier_DefinitionCache_Decorator();
14916:     }
14917: 
14918:     14919: 14920: 14921: 14922: 
14923:     public function add($def, $config)
14924:     {
14925:         return $this->cache->add($def, $config);
14926:     }
14927: 
14928:     14929: 14930: 14931: 14932: 
14933:     public function set($def, $config)
14934:     {
14935:         return $this->cache->set($def, $config);
14936:     }
14937: 
14938:     14939: 14940: 14941: 14942: 
14943:     public function replace($def, $config)
14944:     {
14945:         return $this->cache->replace($def, $config);
14946:     }
14947: 
14948:     14949: 14950: 14951: 
14952:     public function get($config)
14953:     {
14954:         return $this->cache->get($config);
14955:     }
14956: 
14957:     14958: 14959: 14960: 
14961:     public function remove($config)
14962:     {
14963:         return $this->cache->remove($config);
14964:     }
14965: 
14966:     14967: 14968: 14969: 
14970:     public function flush($config)
14971:     {
14972:         return $this->cache->flush($config);
14973:     }
14974: 
14975:     14976: 14977: 14978: 
14979:     public function cleanup($config)
14980:     {
14981:         return $this->cache->cleanup($config);
14982:     }
14983: }
14984: 
14985: 
14986: 
14987: 
14988: 
14989: 14990: 14991: 
14992: class HTMLPurifier_DefinitionCache_Null extends HTMLPurifier_DefinitionCache
14993: {
14994: 
14995:     14996: 14997: 14998: 14999: 
15000:     public function add($def, $config)
15001:     {
15002:         return false;
15003:     }
15004: 
15005:     15006: 15007: 15008: 15009: 
15010:     public function set($def, $config)
15011:     {
15012:         return false;
15013:     }
15014: 
15015:     15016: 15017: 15018: 15019: 
15020:     public function replace($def, $config)
15021:     {
15022:         return false;
15023:     }
15024: 
15025:     15026: 15027: 15028: 
15029:     public function remove($config)
15030:     {
15031:         return false;
15032:     }
15033: 
15034:     15035: 15036: 15037: 
15038:     public function get($config)
15039:     {
15040:         return false;
15041:     }
15042: 
15043:     15044: 15045: 15046: 
15047:     public function flush($config)
15048:     {
15049:         return false;
15050:     }
15051: 
15052:     15053: 15054: 15055: 
15056:     public function cleanup($config)
15057:     {
15058:         return false;
15059:     }
15060: }
15061: 
15062: 
15063: 
15064: 
15065: 
15066: class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCache
15067: {
15068: 
15069:     15070: 15071: 15072: 15073: 
15074:     public function add($def, $config)
15075:     {
15076:         if (!$this->checkDefType($def)) {
15077:             return;
15078:         }
15079:         $file = $this->generateFilePath($config);
15080:         if (file_exists($file)) {
15081:             return false;
15082:         }
15083:         if (!$this->_prepareDir($config)) {
15084:             return false;
15085:         }
15086:         return $this->_write($file, serialize($def), $config);
15087:     }
15088: 
15089:     15090: 15091: 15092: 15093: 
15094:     public function set($def, $config)
15095:     {
15096:         if (!$this->checkDefType($def)) {
15097:             return;
15098:         }
15099:         $file = $this->generateFilePath($config);
15100:         if (!$this->_prepareDir($config)) {
15101:             return false;
15102:         }
15103:         return $this->_write($file, serialize($def), $config);
15104:     }
15105: 
15106:     15107: 15108: 15109: 15110: 
15111:     public function replace($def, $config)
15112:     {
15113:         if (!$this->checkDefType($def)) {
15114:             return;
15115:         }
15116:         $file = $this->generateFilePath($config);
15117:         if (!file_exists($file)) {
15118:             return false;
15119:         }
15120:         if (!$this->_prepareDir($config)) {
15121:             return false;
15122:         }
15123:         return $this->_write($file, serialize($def), $config);
15124:     }
15125: 
15126:     15127: 15128: 15129: 
15130:     public function get($config)
15131:     {
15132:         $file = $this->generateFilePath($config);
15133:         if (!file_exists($file)) {
15134:             return false;
15135:         }
15136:         return unserialize(file_get_contents($file));
15137:     }
15138: 
15139:     15140: 15141: 15142: 
15143:     public function remove($config)
15144:     {
15145:         $file = $this->generateFilePath($config);
15146:         if (!file_exists($file)) {
15147:             return false;
15148:         }
15149:         return unlink($file);
15150:     }
15151: 
15152:     15153: 15154: 15155: 
15156:     public function flush($config)
15157:     {
15158:         if (!$this->_prepareDir($config)) {
15159:             return false;
15160:         }
15161:         $dir = $this->generateDirectoryPath($config);
15162:         $dh = opendir($dir);
15163:         while (false !== ($filename = readdir($dh))) {
15164:             if (empty($filename)) {
15165:                 continue;
15166:             }
15167:             if ($filename[0] === '.') {
15168:                 continue;
15169:             }
15170:             unlink($dir . '/' . $filename);
15171:         }
15172:     }
15173: 
15174:     15175: 15176: 15177: 
15178:     public function cleanup($config)
15179:     {
15180:         if (!$this->_prepareDir($config)) {
15181:             return false;
15182:         }
15183:         $dir = $this->generateDirectoryPath($config);
15184:         $dh = opendir($dir);
15185:         while (false !== ($filename = readdir($dh))) {
15186:             if (empty($filename)) {
15187:                 continue;
15188:             }
15189:             if ($filename[0] === '.') {
15190:                 continue;
15191:             }
15192:             $key = substr($filename, 0, strlen($filename) - 4);
15193:             if ($this->isOld($key, $config)) {
15194:                 unlink($dir . '/' . $filename);
15195:             }
15196:         }
15197:     }
15198: 
15199:     15200: 15201: 15202: 15203: 15204: 15205: 
15206:     public function generateFilePath($config)
15207:     {
15208:         $key = $this->generateKey($config);
15209:         return $this->generateDirectoryPath($config) . '/' . $key . '.ser';
15210:     }
15211: 
15212:     15213: 15214: 15215: 15216: 15217: 15218: 
15219:     public function generateDirectoryPath($config)
15220:     {
15221:         $base = $this->generateBaseDirectoryPath($config);
15222:         return $base . '/' . $this->type;
15223:     }
15224: 
15225:     15226: 15227: 15228: 15229: 15230: 15231: 
15232:     public function generateBaseDirectoryPath($config)
15233:     {
15234:         $base = $config->get('Cache.SerializerPath');
15235:         $base = is_null($base) ? HTMLPURIFIER_PREFIX . '/HTMLPurifier/DefinitionCache/Serializer' : $base;
15236:         return $base;
15237:     }
15238: 
15239:     15240: 15241: 15242: 15243: 15244: 15245: 
15246:     private function _write($file, $data, $config)
15247:     {
15248:         $result = file_put_contents($file, $data);
15249:         if ($result !== false) {
15250:             
15251:             $chmod = $config->get('Cache.SerializerPermissions');
15252:             if (!$chmod) {
15253:                 $chmod = 0644; 
15254:             }
15255:             $chmod = $chmod & 0666;
15256:             chmod($file, $chmod);
15257:         }
15258:         return $result;
15259:     }
15260: 
15261:     15262: 15263: 15264: 15265: 
15266:     private function _prepareDir($config)
15267:     {
15268:         $directory = $this->generateDirectoryPath($config);
15269:         $chmod = $config->get('Cache.SerializerPermissions');
15270:         if (!$chmod) {
15271:             $chmod = 0755; 
15272:         }
15273:         if (!is_dir($directory)) {
15274:             $base = $this->generateBaseDirectoryPath($config);
15275:             if (!is_dir($base)) {
15276:                 trigger_error(
15277:                     'Base directory ' . $base . ' does not exist,
15278:                     please create or change using %Cache.SerializerPath',
15279:                     E_USER_WARNING
15280:                 );
15281:                 return false;
15282:             } elseif (!$this->_testPermissions($base, $chmod)) {
15283:                 return false;
15284:             }
15285:             $old = umask(0000);
15286:             mkdir($directory, $chmod);
15287:             umask($old);
15288:         } elseif (!$this->_testPermissions($directory, $chmod)) {
15289:             return false;
15290:         }
15291:         return true;
15292:     }
15293: 
15294:     15295: 15296: 15297: 15298: 15299: 15300: 
15301:     private function _testPermissions($dir, $chmod)
15302:     {
15303:         
15304:         if (is_writable($dir)) {
15305:             return true;
15306:         }
15307:         if (!is_dir($dir)) {
15308:             
15309:             
15310:             trigger_error(
15311:                 'Directory ' . $dir . ' does not exist',
15312:                 E_USER_WARNING
15313:             );
15314:             return false;
15315:         }
15316:         if (function_exists('posix_getuid')) {
15317:             
15318:             if (fileowner($dir) === posix_getuid()) {
15319:                 
15320:                 $chmod = $chmod | 0700;
15321:                 if (chmod($dir, $chmod)) {
15322:                     return true;
15323:                 }
15324:             } elseif (filegroup($dir) === posix_getgid()) {
15325:                 $chmod = $chmod | 0070;
15326:             } else {
15327:                 
15328:                 
15329:                 $chmod = $chmod | 0777;
15330:             }
15331:             trigger_error(
15332:                 'Directory ' . $dir . ' not writable, ' .
15333:                 'please chmod to ' . decoct($chmod),
15334:                 E_USER_WARNING
15335:             );
15336:         } else {
15337:             
15338:             trigger_error(
15339:                 'Directory ' . $dir . ' not writable, ' .
15340:                 'please alter file permissions',
15341:                 E_USER_WARNING
15342:             );
15343:         }
15344:         return false;
15345:     }
15346: }
15347: 
15348: 
15349: 
15350: 
15351: 
15352: 15353: 15354: 15355: 
15356: class HTMLPurifier_DefinitionCache_Decorator_Cleanup extends HTMLPurifier_DefinitionCache_Decorator
15357: {
15358:     15359: 15360: 
15361:     public $name = 'Cleanup';
15362: 
15363:     15364: 15365: 
15366:     public function copy()
15367:     {
15368:         return new HTMLPurifier_DefinitionCache_Decorator_Cleanup();
15369:     }
15370: 
15371:     15372: 15373: 15374: 15375: 
15376:     public function add($def, $config)
15377:     {
15378:         $status = parent::add($def, $config);
15379:         if (!$status) {
15380:             parent::cleanup($config);
15381:         }
15382:         return $status;
15383:     }
15384: 
15385:     15386: 15387: 15388: 15389: 
15390:     public function set($def, $config)
15391:     {
15392:         $status = parent::set($def, $config);
15393:         if (!$status) {
15394:             parent::cleanup($config);
15395:         }
15396:         return $status;
15397:     }
15398: 
15399:     15400: 15401: 15402: 15403: 
15404:     public function replace($def, $config)
15405:     {
15406:         $status = parent::replace($def, $config);
15407:         if (!$status) {
15408:             parent::cleanup($config);
15409:         }
15410:         return $status;
15411:     }
15412: 
15413:     15414: 15415: 15416: 
15417:     public function get($config)
15418:     {
15419:         $ret = parent::get($config);
15420:         if (!$ret) {
15421:             parent::cleanup($config);
15422:         }
15423:         return $ret;
15424:     }
15425: }
15426: 
15427: 
15428: 
15429: 
15430: 
15431: 15432: 15433: 15434: 15435: 
15436: class HTMLPurifier_DefinitionCache_Decorator_Memory extends HTMLPurifier_DefinitionCache_Decorator
15437: {
15438:     15439: 15440: 
15441:     protected $definitions;
15442: 
15443:     15444: 15445: 
15446:     public $name = 'Memory';
15447: 
15448:     15449: 15450: 
15451:     public function copy()
15452:     {
15453:         return new HTMLPurifier_DefinitionCache_Decorator_Memory();
15454:     }
15455: 
15456:     15457: 15458: 15459: 15460: 
15461:     public function add($def, $config)
15462:     {
15463:         $status = parent::add($def, $config);
15464:         if ($status) {
15465:             $this->definitions[$this->generateKey($config)] = $def;
15466:         }
15467:         return $status;
15468:     }
15469: 
15470:     15471: 15472: 15473: 15474: 
15475:     public function set($def, $config)
15476:     {
15477:         $status = parent::set($def, $config);
15478:         if ($status) {
15479:             $this->definitions[$this->generateKey($config)] = $def;
15480:         }
15481:         return $status;
15482:     }
15483: 
15484:     15485: 15486: 15487: 15488: 
15489:     public function replace($def, $config)
15490:     {
15491:         $status = parent::replace($def, $config);
15492:         if ($status) {
15493:             $this->definitions[$this->generateKey($config)] = $def;
15494:         }
15495:         return $status;
15496:     }
15497: 
15498:     15499: 15500: 15501: 
15502:     public function get($config)
15503:     {
15504:         $key = $this->generateKey($config);
15505:         if (isset($this->definitions[$key])) {
15506:             return $this->definitions[$key];
15507:         }
15508:         $this->definitions[$key] = parent::get($config);
15509:         return $this->definitions[$key];
15510:     }
15511: }
15512: 
15513: 
15514: 
15515: 
15516: 
15517: 15518: 15519: 15520: 
15521: class HTMLPurifier_HTMLModule_Bdo extends HTMLPurifier_HTMLModule
15522: {
15523: 
15524:     15525: 15526: 
15527:     public $name = 'Bdo';
15528: 
15529:     15530: 15531: 
15532:     public $attr_collections = array(
15533:         'I18N' => array('dir' => false)
15534:     );
15535: 
15536:     15537: 15538: 
15539:     public function setup($config)
15540:     {
15541:         $bdo = $this->addElement(
15542:             'bdo',
15543:             'Inline',
15544:             'Inline',
15545:             array('Core', 'Lang'),
15546:             array(
15547:                 'dir' => 'Enum#ltr,rtl', 
15548:                 
15549:                 
15550:             )
15551:         );
15552:         $bdo->attr_transform_post[] = new HTMLPurifier_AttrTransform_BdoDir();
15553: 
15554:         $this->attr_collections['I18N']['dir'] = 'Enum#ltr,rtl';
15555:     }
15556: }
15557: 
15558: 
15559: 
15560: 
15561: 
15562: class HTMLPurifier_HTMLModule_CommonAttributes extends HTMLPurifier_HTMLModule
15563: {
15564:     15565: 15566: 
15567:     public $name = 'CommonAttributes';
15568: 
15569:     15570: 15571: 
15572:     public $attr_collections = array(
15573:         'Core' => array(
15574:             0 => array('Style'),
15575:             
15576:             'class' => 'Class',
15577:             'id' => 'ID',
15578:             'title' => 'CDATA',
15579:         ),
15580:         'Lang' => array(),
15581:         'I18N' => array(
15582:             0 => array('Lang'), 
15583:         ),
15584:         'Common' => array(
15585:             0 => array('Core', 'I18N')
15586:         )
15587:     );
15588: }
15589: 
15590: 
15591: 
15592: 
15593: 
15594: 15595: 15596: 15597: 
15598: class HTMLPurifier_HTMLModule_Edit extends HTMLPurifier_HTMLModule
15599: {
15600: 
15601:     15602: 15603: 
15604:     public $name = 'Edit';
15605: 
15606:     15607: 15608: 
15609:     public function setup($config)
15610:     {
15611:         $contents = 'Chameleon: #PCDATA | Inline ! #PCDATA | Flow';
15612:         $attr = array(
15613:             'cite' => 'URI',
15614:             
15615:         );
15616:         $this->addElement('del', 'Inline', $contents, 'Common', $attr);
15617:         $this->addElement('ins', 'Inline', $contents, 'Common', $attr);
15618:     }
15619: 
15620:     
15621:     
15622:     
15623: 
15624:     
15625:     
15626: 
15627:     15628: 15629: 
15630:     public $defines_child_def = true;
15631: 
15632:     15633: 15634: 15635: 
15636:     public function getChildDef($def)
15637:     {
15638:         if ($def->content_model_type != 'chameleon') {
15639:             return false;
15640:         }
15641:         $value = explode('!', $def->content_model);
15642:         return new HTMLPurifier_ChildDef_Chameleon($value[0], $value[1]);
15643:     }
15644: }
15645: 
15646: 
15647: 
15648: 
15649: 
15650: 15651: 15652: 
15653: class HTMLPurifier_HTMLModule_Forms extends HTMLPurifier_HTMLModule
15654: {
15655:     15656: 15657: 
15658:     public $name = 'Forms';
15659: 
15660:     15661: 15662: 
15663:     public $safe = false;
15664: 
15665:     15666: 15667: 
15668:     public $content_sets = array(
15669:         'Block' => 'Form',
15670:         'Inline' => 'Formctrl',
15671:     );
15672: 
15673:     15674: 15675: 
15676:     public function setup($config)
15677:     {
15678:         $form = $this->addElement(
15679:             'form',
15680:             'Form',
15681:             'Required: Heading | List | Block | fieldset',
15682:             'Common',
15683:             array(
15684:                 'accept' => 'ContentTypes',
15685:                 'accept-charset' => 'Charsets',
15686:                 'action*' => 'URI',
15687:                 'method' => 'Enum#get,post',
15688:                 
15689:                 'enctype' => 'Enum#application/x-www-form-urlencoded,multipart/form-data',
15690:             )
15691:         );
15692:         $form->excludes = array('form' => true);
15693: 
15694:         $input = $this->addElement(
15695:             'input',
15696:             'Formctrl',
15697:             'Empty',
15698:             'Common',
15699:             array(
15700:                 'accept' => 'ContentTypes',
15701:                 'accesskey' => 'Character',
15702:                 'alt' => 'Text',
15703:                 'checked' => 'Bool#checked',
15704:                 'disabled' => 'Bool#disabled',
15705:                 'maxlength' => 'Number',
15706:                 'name' => 'CDATA',
15707:                 'readonly' => 'Bool#readonly',
15708:                 'size' => 'Number',
15709:                 'src' => 'URI#embedded',
15710:                 'tabindex' => 'Number',
15711:                 'type' => 'Enum#text,password,checkbox,button,radio,submit,reset,file,hidden,image',
15712:                 'value' => 'CDATA',
15713:             )
15714:         );
15715:         $input->attr_transform_post[] = new HTMLPurifier_AttrTransform_Input();
15716: 
15717:         $this->addElement(
15718:             'select',
15719:             'Formctrl',
15720:             'Required: optgroup | option',
15721:             'Common',
15722:             array(
15723:                 'disabled' => 'Bool#disabled',
15724:                 'multiple' => 'Bool#multiple',
15725:                 'name' => 'CDATA',
15726:                 'size' => 'Number',
15727:                 'tabindex' => 'Number',
15728:             )
15729:         );
15730: 
15731:         $this->addElement(
15732:             'option',
15733:             false,
15734:             'Optional: #PCDATA',
15735:             'Common',
15736:             array(
15737:                 'disabled' => 'Bool#disabled',
15738:                 'label' => 'Text',
15739:                 'selected' => 'Bool#selected',
15740:                 'value' => 'CDATA',
15741:             )
15742:         );
15743:         
15744:         
15745:         
15746: 
15747:         $textarea = $this->addElement(
15748:             'textarea',
15749:             'Formctrl',
15750:             'Optional: #PCDATA',
15751:             'Common',
15752:             array(
15753:                 'accesskey' => 'Character',
15754:                 'cols*' => 'Number',
15755:                 'disabled' => 'Bool#disabled',
15756:                 'name' => 'CDATA',
15757:                 'readonly' => 'Bool#readonly',
15758:                 'rows*' => 'Number',
15759:                 'tabindex' => 'Number',
15760:             )
15761:         );
15762:         $textarea->attr_transform_pre[] = new HTMLPurifier_AttrTransform_Textarea();
15763: 
15764:         $button = $this->addElement(
15765:             'button',
15766:             'Formctrl',
15767:             'Optional: #PCDATA | Heading | List | Block | Inline',
15768:             'Common',
15769:             array(
15770:                 'accesskey' => 'Character',
15771:                 'disabled' => 'Bool#disabled',
15772:                 'name' => 'CDATA',
15773:                 'tabindex' => 'Number',
15774:                 'type' => 'Enum#button,submit,reset',
15775:                 'value' => 'CDATA',
15776:             )
15777:         );
15778: 
15779:         
15780:         $button->excludes = $this->makeLookup(
15781:             'form',
15782:             'fieldset', 
15783:             'input',
15784:             'select',
15785:             'textarea',
15786:             'label',
15787:             'button', 
15788:             'a', 
15789:             'isindex',
15790:             'iframe' 
15791:         );
15792: 
15793:         
15794:         
15795:         
15796: 
15797:         
15798:         $this->addElement('fieldset', 'Form', 'Custom: (#WS?,legend,(Flow|#PCDATA)*)', 'Common');
15799: 
15800:         $label = $this->addElement(
15801:             'label',
15802:             'Formctrl',
15803:             'Optional: #PCDATA | Inline',
15804:             'Common',
15805:             array(
15806:                 'accesskey' => 'Character',
15807:                 
15808:             )
15809:         );
15810:         $label->excludes = array('label' => true);
15811: 
15812:         $this->addElement(
15813:             'legend',
15814:             false,
15815:             'Optional: #PCDATA | Inline',
15816:             'Common',
15817:             array(
15818:                 'accesskey' => 'Character',
15819:             )
15820:         );
15821: 
15822:         $this->addElement(
15823:             'optgroup',
15824:             false,
15825:             'Required: option',
15826:             'Common',
15827:             array(
15828:                 'disabled' => 'Bool#disabled',
15829:                 'label*' => 'Text',
15830:             )
15831:         );
15832:         
15833:         
15834:     }
15835: }
15836: 
15837: 
15838: 
15839: 
15840: 
15841: 15842: 15843: 
15844: class HTMLPurifier_HTMLModule_Hypertext extends HTMLPurifier_HTMLModule
15845: {
15846: 
15847:     15848: 15849: 
15850:     public $name = 'Hypertext';
15851: 
15852:     15853: 15854: 
15855:     public function setup($config)
15856:     {
15857:         $a = $this->addElement(
15858:             'a',
15859:             'Inline',
15860:             'Inline',
15861:             'Common',
15862:             array(
15863:                 
15864:                 
15865:                 'href' => 'URI',
15866:                 
15867:                 'rel' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rel'),
15868:                 'rev' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rev'),
15869:                 
15870:                 
15871:             )
15872:         );
15873:         $a->formatting = true;
15874:         $a->excludes = array('a' => true);
15875:     }
15876: }
15877: 
15878: 
15879: 
15880: 
15881: 
15882: 15883: 15884: 15885: 15886: 15887: 15888: 
15889: class HTMLPurifier_HTMLModule_Iframe extends HTMLPurifier_HTMLModule
15890: {
15891: 
15892:     15893: 15894: 
15895:     public $name = 'Iframe';
15896: 
15897:     15898: 15899: 
15900:     public $safe = false;
15901: 
15902:     15903: 15904: 
15905:     public function setup($config)
15906:     {
15907:         if ($config->get('HTML.SafeIframe')) {
15908:             $this->safe = true;
15909:         }
15910:         $this->addElement(
15911:             'iframe',
15912:             'Inline',
15913:             'Flow',
15914:             'Common',
15915:             array(
15916:                 'src' => 'URI#embedded',
15917:                 'width' => 'Length',
15918:                 'height' => 'Length',
15919:                 'name' => 'ID',
15920:                 'scrolling' => 'Enum#yes,no,auto',
15921:                 'frameborder' => 'Enum#0,1',
15922:                 'longdesc' => 'URI',
15923:                 'marginheight' => 'Pixels',
15924:                 'marginwidth' => 'Pixels',
15925:             )
15926:         );
15927:     }
15928: }
15929: 
15930: 
15931: 
15932: 
15933: 
15934: 15935: 15936: 15937: 15938: 
15939: class HTMLPurifier_HTMLModule_Image extends HTMLPurifier_HTMLModule
15940: {
15941: 
15942:     15943: 15944: 
15945:     public $name = 'Image';
15946: 
15947:     15948: 15949: 
15950:     public function setup($config)
15951:     {
15952:         $max = $config->get('HTML.MaxImgLength');
15953:         $img = $this->addElement(
15954:             'img',
15955:             'Inline',
15956:             'Empty',
15957:             'Common',
15958:             array(
15959:                 'alt*' => 'Text',
15960:                 
15961:                 
15962:                 'height' => 'Pixels#' . $max,
15963:                 'width' => 'Pixels#' . $max,
15964:                 'longdesc' => 'URI',
15965:                 'src*' => new HTMLPurifier_AttrDef_URI(true), 
15966:             )
15967:         );
15968:         if ($max === null || $config->get('HTML.Trusted')) {
15969:             $img->attr['height'] =
15970:             $img->attr['width'] = 'Length';
15971:         }
15972: 
15973:         
15974:         $img->attr_transform_pre[] =
15975:         $img->attr_transform_post[] =
15976:             new HTMLPurifier_AttrTransform_ImgRequired();
15977:     }
15978: }
15979: 
15980: 
15981: 
15982: 
15983: 
15984: 15985: 15986: 15987: 15988: 15989: 15990: 15991: 15992: 15993: 15994: 15995: 15996: 15997: 15998: 
15999: 
16000: class HTMLPurifier_HTMLModule_Legacy extends HTMLPurifier_HTMLModule
16001: {
16002:     16003: 16004: 
16005:     public $name = 'Legacy';
16006: 
16007:     16008: 16009: 
16010:     public function setup($config)
16011:     {
16012:         $this->addElement(
16013:             'basefont',
16014:             'Inline',
16015:             'Empty',
16016:             null,
16017:             array(
16018:                 'color' => 'Color',
16019:                 'face' => 'Text', 
16020:                 'size' => 'Text', 
16021:                 'id' => 'ID'
16022:             )
16023:         );
16024:         $this->addElement('center', 'Block', 'Flow', 'Common');
16025:         $this->addElement(
16026:             'dir',
16027:             'Block',
16028:             'Required: li',
16029:             'Common',
16030:             array(
16031:                 'compact' => 'Bool#compact'
16032:             )
16033:         );
16034:         $this->addElement(
16035:             'font',
16036:             'Inline',
16037:             'Inline',
16038:             array('Core', 'I18N'),
16039:             array(
16040:                 'color' => 'Color',
16041:                 'face' => 'Text', 
16042:                 'size' => 'Text', 
16043:             )
16044:         );
16045:         $this->addElement(
16046:             'menu',
16047:             'Block',
16048:             'Required: li',
16049:             'Common',
16050:             array(
16051:                 'compact' => 'Bool#compact'
16052:             )
16053:         );
16054: 
16055:         $s = $this->addElement('s', 'Inline', 'Inline', 'Common');
16056:         $s->formatting = true;
16057: 
16058:         $strike = $this->addElement('strike', 'Inline', 'Inline', 'Common');
16059:         $strike->formatting = true;
16060: 
16061:         $u = $this->addElement('u', 'Inline', 'Inline', 'Common');
16062:         $u->formatting = true;
16063: 
16064:         
16065: 
16066:         $align = 'Enum#left,right,center,justify';
16067: 
16068:         $address = $this->addBlankElement('address');
16069:         $address->content_model = 'Inline | #PCDATA | p';
16070:         $address->content_model_type = 'optional';
16071:         $address->child = false;
16072: 
16073:         $blockquote = $this->addBlankElement('blockquote');
16074:         $blockquote->content_model = 'Flow | #PCDATA';
16075:         $blockquote->content_model_type = 'optional';
16076:         $blockquote->child = false;
16077: 
16078:         $br = $this->addBlankElement('br');
16079:         $br->attr['clear'] = 'Enum#left,all,right,none';
16080: 
16081:         $caption = $this->addBlankElement('caption');
16082:         $caption->attr['align'] = 'Enum#top,bottom,left,right';
16083: 
16084:         $div = $this->addBlankElement('div');
16085:         $div->attr['align'] = $align;
16086: 
16087:         $dl = $this->addBlankElement('dl');
16088:         $dl->attr['compact'] = 'Bool#compact';
16089: 
16090:         for ($i = 1; $i <= 6; $i++) {
16091:             $h = $this->addBlankElement("h$i");
16092:             $h->attr['align'] = $align;
16093:         }
16094: 
16095:         $hr = $this->addBlankElement('hr');
16096:         $hr->attr['align'] = $align;
16097:         $hr->attr['noshade'] = 'Bool#noshade';
16098:         $hr->attr['size'] = 'Pixels';
16099:         $hr->attr['width'] = 'Length';
16100: 
16101:         $img = $this->addBlankElement('img');
16102:         $img->attr['align'] = 'IAlign';
16103:         $img->attr['border'] = 'Pixels';
16104:         $img->attr['hspace'] = 'Pixels';
16105:         $img->attr['vspace'] = 'Pixels';
16106: 
16107:         
16108: 
16109:         $li = $this->addBlankElement('li');
16110:         $li->attr['value'] = new HTMLPurifier_AttrDef_Integer();
16111:         $li->attr['type'] = 'Enum#s:1,i,I,a,A,disc,square,circle';
16112: 
16113:         $ol = $this->addBlankElement('ol');
16114:         $ol->attr['compact'] = 'Bool#compact';
16115:         $ol->attr['start'] = new HTMLPurifier_AttrDef_Integer();
16116:         $ol->attr['type'] = 'Enum#s:1,i,I,a,A';
16117: 
16118:         $p = $this->addBlankElement('p');
16119:         $p->attr['align'] = $align;
16120: 
16121:         $pre = $this->addBlankElement('pre');
16122:         $pre->attr['width'] = 'Number';
16123: 
16124:         
16125: 
16126:         $table = $this->addBlankElement('table');
16127:         $table->attr['align'] = 'Enum#left,center,right';
16128:         $table->attr['bgcolor'] = 'Color';
16129: 
16130:         $tr = $this->addBlankElement('tr');
16131:         $tr->attr['bgcolor'] = 'Color';
16132: 
16133:         $th = $this->addBlankElement('th');
16134:         $th->attr['bgcolor'] = 'Color';
16135:         $th->attr['height'] = 'Length';
16136:         $th->attr['nowrap'] = 'Bool#nowrap';
16137:         $th->attr['width'] = 'Length';
16138: 
16139:         $td = $this->addBlankElement('td');
16140:         $td->attr['bgcolor'] = 'Color';
16141:         $td->attr['height'] = 'Length';
16142:         $td->attr['nowrap'] = 'Bool#nowrap';
16143:         $td->attr['width'] = 'Length';
16144: 
16145:         $ul = $this->addBlankElement('ul');
16146:         $ul->attr['compact'] = 'Bool#compact';
16147:         $ul->attr['type'] = 'Enum#square,disc,circle';
16148: 
16149:         
16150:         
16151:         
16152:         
16153: 
16154:         $form = $this->addBlankElement('form');
16155:         $form->content_model = 'Flow | #PCDATA';
16156:         $form->content_model_type = 'optional';
16157:         $form->attr['target'] = 'FrameTarget';
16158: 
16159:         $input = $this->addBlankElement('input');
16160:         $input->attr['align'] = 'IAlign';
16161: 
16162:         $legend = $this->addBlankElement('legend');
16163:         $legend->attr['align'] = 'LAlign';
16164:     }
16165: }
16166: 
16167: 
16168: 
16169: 
16170: 
16171: 16172: 16173: 
16174: class HTMLPurifier_HTMLModule_List extends HTMLPurifier_HTMLModule
16175: {
16176:     16177: 16178: 
16179:     public $name = 'List';
16180: 
16181:     
16182:     
16183:     
16184:     
16185:     
16186:     
16187:     
16188:     
16189: 
16190:     16191: 16192: 
16193:     public $content_sets = array('Flow' => 'List');
16194: 
16195:     16196: 16197: 
16198:     public function setup($config)
16199:     {
16200:         $ol = $this->addElement('ol', 'List', new HTMLPurifier_ChildDef_List(), 'Common');
16201:         $ul = $this->addElement('ul', 'List', new HTMLPurifier_ChildDef_List(), 'Common');
16202:         
16203:         
16204:         
16205:         
16206:         
16207:         
16208:         $ol->wrap = 'li';
16209:         $ul->wrap = 'li';
16210:         $this->addElement('dl', 'List', 'Required: dt | dd', 'Common');
16211: 
16212:         $this->addElement('li', false, 'Flow', 'Common');
16213: 
16214:         $this->addElement('dd', false, 'Flow', 'Common');
16215:         $this->addElement('dt', false, 'Inline', 'Common');
16216:     }
16217: }
16218: 
16219: 
16220: 
16221: 
16222: 
16223: class HTMLPurifier_HTMLModule_Name extends HTMLPurifier_HTMLModule
16224: {
16225:     16226: 16227: 
16228:     public $name = 'Name';
16229: 
16230:     16231: 16232: 
16233:     public function setup($config)
16234:     {
16235:         $elements = array('a', 'applet', 'form', 'frame', 'iframe', 'img', 'map');
16236:         foreach ($elements as $name) {
16237:             $element = $this->addBlankElement($name);
16238:             $element->attr['name'] = 'CDATA';
16239:             if (!$config->get('HTML.Attr.Name.UseCDATA')) {
16240:                 $element->attr_transform_post[] = new HTMLPurifier_AttrTransform_NameSync();
16241:             }
16242:         }
16243:     }
16244: }
16245: 
16246: 
16247: 
16248: 
16249: 
16250: 16251: 16252: 16253: 
16254: class HTMLPurifier_HTMLModule_Nofollow extends HTMLPurifier_HTMLModule
16255: {
16256: 
16257:     16258: 16259: 
16260:     public $name = 'Nofollow';
16261: 
16262:     16263: 16264: 
16265:     public function setup($config)
16266:     {
16267:         $a = $this->addBlankElement('a');
16268:         $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_Nofollow();
16269:     }
16270: }
16271: 
16272: 
16273: 
16274: 
16275: 
16276: class HTMLPurifier_HTMLModule_NonXMLCommonAttributes extends HTMLPurifier_HTMLModule
16277: {
16278:     16279: 16280: 
16281:     public $name = 'NonXMLCommonAttributes';
16282: 
16283:     16284: 16285: 
16286:     public $attr_collections = array(
16287:         'Lang' => array(
16288:             'lang' => 'LanguageCode',
16289:         )
16290:     );
16291: }
16292: 
16293: 
16294: 
16295: 
16296: 
16297: 16298: 16299: 16300: 16301: 
16302: class HTMLPurifier_HTMLModule_Object extends HTMLPurifier_HTMLModule
16303: {
16304:     16305: 16306: 
16307:     public $name = 'Object';
16308: 
16309:     16310: 16311: 
16312:     public $safe = false;
16313: 
16314:     16315: 16316: 
16317:     public function setup($config)
16318:     {
16319:         $this->addElement(
16320:             'object',
16321:             'Inline',
16322:             'Optional: #PCDATA | Flow | param',
16323:             'Common',
16324:             array(
16325:                 'archive' => 'URI',
16326:                 'classid' => 'URI',
16327:                 'codebase' => 'URI',
16328:                 'codetype' => 'Text',
16329:                 'data' => 'URI',
16330:                 'declare' => 'Bool#declare',
16331:                 'height' => 'Length',
16332:                 'name' => 'CDATA',
16333:                 'standby' => 'Text',
16334:                 'tabindex' => 'Number',
16335:                 'type' => 'ContentType',
16336:                 'width' => 'Length'
16337:             )
16338:         );
16339: 
16340:         $this->addElement(
16341:             'param',
16342:             false,
16343:             'Empty',
16344:             null,
16345:             array(
16346:                 'id' => 'ID',
16347:                 'name*' => 'Text',
16348:                 'type' => 'Text',
16349:                 'value' => 'Text',
16350:                 'valuetype' => 'Enum#data,ref,object'
16351:             )
16352:         );
16353:     }
16354: }
16355: 
16356: 
16357: 
16358: 
16359: 
16360: 16361: 16362: 16363: 16364: 16365: 16366: 16367: 16368: 16369: 
16370: class HTMLPurifier_HTMLModule_Presentation extends HTMLPurifier_HTMLModule
16371: {
16372: 
16373:     16374: 16375: 
16376:     public $name = 'Presentation';
16377: 
16378:     16379: 16380: 
16381:     public function setup($config)
16382:     {
16383:         $this->addElement('hr', 'Block', 'Empty', 'Common');
16384:         $this->addElement('sub', 'Inline', 'Inline', 'Common');
16385:         $this->addElement('sup', 'Inline', 'Inline', 'Common');
16386:         $b = $this->addElement('b', 'Inline', 'Inline', 'Common');
16387:         $b->formatting = true;
16388:         $big = $this->addElement('big', 'Inline', 'Inline', 'Common');
16389:         $big->formatting = true;
16390:         $i = $this->addElement('i', 'Inline', 'Inline', 'Common');
16391:         $i->formatting = true;
16392:         $small = $this->addElement('small', 'Inline', 'Inline', 'Common');
16393:         $small->formatting = true;
16394:         $tt = $this->addElement('tt', 'Inline', 'Inline', 'Common');
16395:         $tt->formatting = true;
16396:     }
16397: }
16398: 
16399: 
16400: 
16401: 
16402: 
16403: 16404: 16405: 16406: 
16407: class HTMLPurifier_HTMLModule_Proprietary extends HTMLPurifier_HTMLModule
16408: {
16409:     16410: 16411: 
16412:     public $name = 'Proprietary';
16413: 
16414:     16415: 16416: 
16417:     public function setup($config)
16418:     {
16419:         $this->addElement(
16420:             'marquee',
16421:             'Inline',
16422:             'Flow',
16423:             'Common',
16424:             array(
16425:                 'direction' => 'Enum#left,right,up,down',
16426:                 'behavior' => 'Enum#alternate',
16427:                 'width' => 'Length',
16428:                 'height' => 'Length',
16429:                 'scrolldelay' => 'Number',
16430:                 'scrollamount' => 'Number',
16431:                 'loop' => 'Number',
16432:                 'bgcolor' => 'Color',
16433:                 'hspace' => 'Pixels',
16434:                 'vspace' => 'Pixels',
16435:             )
16436:         );
16437:     }
16438: }
16439: 
16440: 
16441: 
16442: 
16443: 
16444: 16445: 16446: 16447: 
16448: class HTMLPurifier_HTMLModule_Ruby extends HTMLPurifier_HTMLModule
16449: {
16450: 
16451:     16452: 16453: 
16454:     public $name = 'Ruby';
16455: 
16456:     16457: 16458: 
16459:     public function setup($config)
16460:     {
16461:         $this->addElement(
16462:             'ruby',
16463:             'Inline',
16464:             'Custom: ((rb, (rt | (rp, rt, rp))) | (rbc, rtc, rtc?))',
16465:             'Common'
16466:         );
16467:         $this->addElement('rbc', false, 'Required: rb', 'Common');
16468:         $this->addElement('rtc', false, 'Required: rt', 'Common');
16469:         $rb = $this->addElement('rb', false, 'Inline', 'Common');
16470:         $rb->excludes = array('ruby' => true);
16471:         $rt = $this->addElement('rt', false, 'Inline', 'Common', array('rbspan' => 'Number'));
16472:         $rt->excludes = array('ruby' => true);
16473:         $this->addElement('rp', false, 'Optional: #PCDATA', 'Common');
16474:     }
16475: }
16476: 
16477: 
16478: 
16479: 
16480: 
16481: 16482: 16483: 
16484: class HTMLPurifier_HTMLModule_SafeEmbed extends HTMLPurifier_HTMLModule
16485: {
16486:     16487: 16488: 
16489:     public $name = 'SafeEmbed';
16490: 
16491:     16492: 16493: 
16494:     public function setup($config)
16495:     {
16496:         $max = $config->get('HTML.MaxImgLength');
16497:         $embed = $this->addElement(
16498:             'embed',
16499:             'Inline',
16500:             'Empty',
16501:             'Common',
16502:             array(
16503:                 'src*' => 'URI#embedded',
16504:                 'type' => 'Enum#application/x-shockwave-flash',
16505:                 'width' => 'Pixels#' . $max,
16506:                 'height' => 'Pixels#' . $max,
16507:                 'allowscriptaccess' => 'Enum#never',
16508:                 'allownetworking' => 'Enum#internal',
16509:                 'flashvars' => 'Text',
16510:                 'wmode' => 'Enum#window,transparent,opaque',
16511:                 'name' => 'ID',
16512:             )
16513:         );
16514:         $embed->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeEmbed();
16515:     }
16516: }
16517: 
16518: 
16519: 
16520: 
16521: 
16522: 16523: 16524: 16525: 16526: 16527: 
16528: class HTMLPurifier_HTMLModule_SafeObject extends HTMLPurifier_HTMLModule
16529: {
16530:     16531: 16532: 
16533:     public $name = 'SafeObject';
16534: 
16535:     16536: 16537: 
16538:     public function setup($config)
16539:     {
16540:         
16541:         
16542: 
16543:         $max = $config->get('HTML.MaxImgLength');
16544:         $object = $this->addElement(
16545:             'object',
16546:             'Inline',
16547:             'Optional: param | Flow | #PCDATA',
16548:             'Common',
16549:             array(
16550:                 
16551:                 
16552:                 'type' => 'Enum#application/x-shockwave-flash',
16553:                 'width' => 'Pixels#' . $max,
16554:                 'height' => 'Pixels#' . $max,
16555:                 'data' => 'URI#embedded',
16556:                 'codebase' => new HTMLPurifier_AttrDef_Enum(
16557:                     array(
16558:                         'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0'
16559:                     )
16560:                 ),
16561:             )
16562:         );
16563:         $object->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeObject();
16564: 
16565:         $param = $this->addElement(
16566:             'param',
16567:             false,
16568:             'Empty',
16569:             false,
16570:             array(
16571:                 'id' => 'ID',
16572:                 'name*' => 'Text',
16573:                 'value' => 'Text'
16574:             )
16575:         );
16576:         $param->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeParam();
16577:         $this->info_injector[] = 'SafeObject';
16578:     }
16579: }
16580: 
16581: 
16582: 
16583: 
16584: 
16585: 16586: 16587: 16588: 
16589: class HTMLPurifier_HTMLModule_SafeScripting extends HTMLPurifier_HTMLModule
16590: {
16591:     16592: 16593: 
16594:     public $name = 'SafeScripting';
16595: 
16596:     16597: 16598: 
16599:     public function setup($config)
16600:     {
16601:         
16602:         
16603: 
16604:         $allowed = $config->get('HTML.SafeScripting');
16605:         $script = $this->addElement(
16606:             'script',
16607:             'Inline',
16608:             'Empty',
16609:             null,
16610:             array(
16611:                 
16612:                 
16613:                 'type' => 'Enum#text/javascript',
16614:                 'src*' => new HTMLPurifier_AttrDef_Enum(array_keys($allowed))
16615:             )
16616:         );
16617:         $script->attr_transform_pre[] =
16618:         $script->attr_transform_post[] = new HTMLPurifier_AttrTransform_ScriptRequired();
16619:     }
16620: }
16621: 
16622: 
16623: 
16624: 
16625: 
16626: 16627: 16628: 16629: 16630: 16631: 
16632: 
16633: 16634: 16635: 16636: 16637: 16638: 
16639: class HTMLPurifier_HTMLModule_Scripting extends HTMLPurifier_HTMLModule
16640: {
16641:     16642: 16643: 
16644:     public $name = 'Scripting';
16645: 
16646:     16647: 16648: 
16649:     public $elements = array('script', 'noscript');
16650: 
16651:     16652: 16653: 
16654:     public $content_sets = array('Block' => 'script | noscript', 'Inline' => 'script | noscript');
16655: 
16656:     16657: 16658: 
16659:     public $safe = false;
16660: 
16661:     16662: 16663: 
16664:     public function setup($config)
16665:     {
16666:         
16667:         
16668:         
16669:         
16670:         
16671: 
16672:         
16673:         
16674: 
16675:         
16676:         
16677:         $this->info['noscript'] = new HTMLPurifier_ElementDef();
16678:         $this->info['noscript']->attr = array(0 => array('Common'));
16679:         $this->info['noscript']->content_model = 'Heading | List | Block';
16680:         $this->info['noscript']->content_model_type = 'required';
16681: 
16682:         $this->info['script'] = new HTMLPurifier_ElementDef();
16683:         $this->info['script']->attr = array(
16684:             'defer' => new HTMLPurifier_AttrDef_Enum(array('defer')),
16685:             'src' => new HTMLPurifier_AttrDef_URI(true),
16686:             'type' => new HTMLPurifier_AttrDef_Enum(array('text/javascript'))
16687:         );
16688:         $this->info['script']->content_model = '#PCDATA';
16689:         $this->info['script']->content_model_type = 'optional';
16690:         $this->info['script']->attr_transform_pre[] =
16691:         $this->info['script']->attr_transform_post[] =
16692:             new HTMLPurifier_AttrTransform_ScriptRequired();
16693:     }
16694: }
16695: 
16696: 
16697: 
16698: 
16699: 
16700: 16701: 16702: 16703: 
16704: class HTMLPurifier_HTMLModule_StyleAttribute extends HTMLPurifier_HTMLModule
16705: {
16706:     16707: 16708: 
16709:     public $name = 'StyleAttribute';
16710: 
16711:     16712: 16713: 
16714:     public $attr_collections = array(
16715:         
16716:         
16717:         'Style' => array('style' => false), 
16718:         'Core' => array(0 => array('Style'))
16719:     );
16720: 
16721:     16722: 16723: 
16724:     public function setup($config)
16725:     {
16726:         $this->attr_collections['Style']['style'] = new HTMLPurifier_AttrDef_CSS();
16727:     }
16728: }
16729: 
16730: 
16731: 
16732: 
16733: 
16734: 16735: 16736: 
16737: class HTMLPurifier_HTMLModule_Tables extends HTMLPurifier_HTMLModule
16738: {
16739:     16740: 16741: 
16742:     public $name = 'Tables';
16743: 
16744:     16745: 16746: 
16747:     public function setup($config)
16748:     {
16749:         $this->addElement('caption', false, 'Inline', 'Common');
16750: 
16751:         $this->addElement(
16752:             'table',
16753:             'Block',
16754:             new HTMLPurifier_ChildDef_Table(),
16755:             'Common',
16756:             array(
16757:                 'border' => 'Pixels',
16758:                 'cellpadding' => 'Length',
16759:                 'cellspacing' => 'Length',
16760:                 'frame' => 'Enum#void,above,below,hsides,lhs,rhs,vsides,box,border',
16761:                 'rules' => 'Enum#none,groups,rows,cols,all',
16762:                 'summary' => 'Text',
16763:                 'width' => 'Length'
16764:             )
16765:         );
16766: 
16767:         
16768:         $cell_align = array(
16769:             'align' => 'Enum#left,center,right,justify,char',
16770:             'charoff' => 'Length',
16771:             'valign' => 'Enum#top,middle,bottom,baseline',
16772:         );
16773: 
16774:         $cell_t = array_merge(
16775:             array(
16776:                 'abbr' => 'Text',
16777:                 'colspan' => 'Number',
16778:                 'rowspan' => 'Number',
16779:                 
16780:                 
16781:                 'scope' => 'Enum#row,col,rowgroup,colgroup',
16782:             ),
16783:             $cell_align
16784:         );
16785:         $this->addElement('td', false, 'Flow', 'Common', $cell_t);
16786:         $this->addElement('th', false, 'Flow', 'Common', $cell_t);
16787: 
16788:         $this->addElement('tr', false, 'Required: td | th', 'Common', $cell_align);
16789: 
16790:         $cell_col = array_merge(
16791:             array(
16792:                 'span' => 'Number',
16793:                 'width' => 'MultiLength',
16794:             ),
16795:             $cell_align
16796:         );
16797:         $this->addElement('col', false, 'Empty', 'Common', $cell_col);
16798:         $this->addElement('colgroup', false, 'Optional: col', 'Common', $cell_col);
16799: 
16800:         $this->addElement('tbody', false, 'Required: tr', 'Common', $cell_align);
16801:         $this->addElement('thead', false, 'Required: tr', 'Common', $cell_align);
16802:         $this->addElement('tfoot', false, 'Required: tr', 'Common', $cell_align);
16803:     }
16804: }
16805: 
16806: 
16807: 
16808: 
16809: 
16810: 16811: 16812: 
16813: class HTMLPurifier_HTMLModule_Target extends HTMLPurifier_HTMLModule
16814: {
16815:     16816: 16817: 
16818:     public $name = 'Target';
16819: 
16820:     16821: 16822: 
16823:     public function setup($config)
16824:     {
16825:         $elements = array('a');
16826:         foreach ($elements as $name) {
16827:             $e = $this->addBlankElement($name);
16828:             $e->attr = array(
16829:                 'target' => new HTMLPurifier_AttrDef_HTML_FrameTarget()
16830:             );
16831:         }
16832:     }
16833: }
16834: 
16835: 
16836: 
16837: 
16838: 
16839: 16840: 16841: 16842: 
16843: class HTMLPurifier_HTMLModule_TargetBlank extends HTMLPurifier_HTMLModule
16844: {
16845:     16846: 16847: 
16848:     public $name = 'TargetBlank';
16849: 
16850:     16851: 16852: 
16853:     public function setup($config)
16854:     {
16855:         $a = $this->addBlankElement('a');
16856:         $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetBlank();
16857:     }
16858: }
16859: 
16860: 
16861: 
16862: 
16863: 
16864: 16865: 16866: 16867: 16868: 16869: 16870: 16871: 16872: 16873: 16874: 16875: 
16876: class HTMLPurifier_HTMLModule_Text extends HTMLPurifier_HTMLModule
16877: {
16878:     16879: 16880: 
16881:     public $name = 'Text';
16882: 
16883:     16884: 16885: 
16886:     public $content_sets = array(
16887:         'Flow' => 'Heading | Block | Inline'
16888:     );
16889: 
16890:     16891: 16892: 
16893:     public function setup($config)
16894:     {
16895:         
16896:         $this->addElement('abbr', 'Inline', 'Inline', 'Common');
16897:         $this->addElement('acronym', 'Inline', 'Inline', 'Common');
16898:         $this->addElement('cite', 'Inline', 'Inline', 'Common');
16899:         $this->addElement('dfn', 'Inline', 'Inline', 'Common');
16900:         $this->addElement('kbd', 'Inline', 'Inline', 'Common');
16901:         $this->addElement('q', 'Inline', 'Inline', 'Common', array('cite' => 'URI'));
16902:         $this->addElement('samp', 'Inline', 'Inline', 'Common');
16903:         $this->addElement('var', 'Inline', 'Inline', 'Common');
16904: 
16905:         $em = $this->addElement('em', 'Inline', 'Inline', 'Common');
16906:         $em->formatting = true;
16907: 
16908:         $strong = $this->addElement('strong', 'Inline', 'Inline', 'Common');
16909:         $strong->formatting = true;
16910: 
16911:         $code = $this->addElement('code', 'Inline', 'Inline', 'Common');
16912:         $code->formatting = true;
16913: 
16914:         
16915:         $this->addElement('span', 'Inline', 'Inline', 'Common');
16916:         $this->addElement('br', 'Inline', 'Empty', 'Core');
16917: 
16918:         
16919:         $this->addElement('address', 'Block', 'Inline', 'Common');
16920:         $this->addElement('blockquote', 'Block', 'Optional: Heading | Block | List', 'Common', array('cite' => 'URI'));
16921:         $pre = $this->addElement('pre', 'Block', 'Inline', 'Common');
16922:         $pre->excludes = $this->makeLookup(
16923:             'img',
16924:             'big',
16925:             'small',
16926:             'object',
16927:             'applet',
16928:             'font',
16929:             'basefont'
16930:         );
16931:         $this->addElement('h1', 'Heading', 'Inline', 'Common');
16932:         $this->addElement('h2', 'Heading', 'Inline', 'Common');
16933:         $this->addElement('h3', 'Heading', 'Inline', 'Common');
16934:         $this->addElement('h4', 'Heading', 'Inline', 'Common');
16935:         $this->addElement('h5', 'Heading', 'Inline', 'Common');
16936:         $this->addElement('h6', 'Heading', 'Inline', 'Common');
16937: 
16938:         
16939:         $p = $this->addElement('p', 'Block', 'Inline', 'Common');
16940:         $p->autoclose = array_flip(
16941:             array("address", "blockquote", "center", "dir", "div", "dl", "fieldset", "ol", "p", "ul")
16942:         );
16943: 
16944:         $this->addElement('div', 'Block', 'Flow', 'Common');
16945:     }
16946: }
16947: 
16948: 
16949: 
16950: 
16951: 
16952: 16953: 16954: 16955: 16956: 
16957: class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule
16958: {
16959:     16960: 16961: 16962: 16963: 
16964:     public $levels = array(0 => 'none', 'light', 'medium', 'heavy');
16965: 
16966:     16967: 16968: 16969: 16970: 
16971:     public $defaultLevel = null;
16972: 
16973:     16974: 16975: 16976: 16977: 16978: 
16979:     public $fixesForLevel = array(
16980:         'light' => array(),
16981:         'medium' => array(),
16982:         'heavy' => array()
16983:     );
16984: 
16985:     16986: 16987: 16988: 16989: 16990: 16991: 
16992:     public function setup($config)
16993:     {
16994:         
16995:         $fixes = $this->makeFixes();
16996:         $this->makeFixesForLevel($fixes);
16997: 
16998:         
16999:         $level = $config->get('HTML.TidyLevel');
17000:         $fixes_lookup = $this->getFixesForLevel($level);
17001: 
17002:         
17003:         $add_fixes = $config->get('HTML.TidyAdd');
17004:         $remove_fixes = $config->get('HTML.TidyRemove');
17005: 
17006:         foreach ($fixes as $name => $fix) {
17007:             
17008:             if (isset($remove_fixes[$name]) ||
17009:                 (!isset($add_fixes[$name]) && !isset($fixes_lookup[$name]))) {
17010:                 unset($fixes[$name]);
17011:             }
17012:         }
17013: 
17014:         
17015:         $this->populate($fixes);
17016:     }
17017: 
17018:     17019: 17020: 17021: 17022: 17023: 
17024:     public function getFixesForLevel($level)
17025:     {
17026:         if ($level == $this->levels[0]) {
17027:             return array();
17028:         }
17029:         $activated_levels = array();
17030:         for ($i = 1, $c = count($this->levels); $i < $c; $i++) {
17031:             $activated_levels[] = $this->levels[$i];
17032:             if ($this->levels[$i] == $level) {
17033:                 break;
17034:             }
17035:         }
17036:         if ($i == $c) {
17037:             trigger_error(
17038:                 'Tidy level ' . htmlspecialchars($level) . ' not recognized',
17039:                 E_USER_WARNING
17040:             );
17041:             return array();
17042:         }
17043:         $ret = array();
17044:         foreach ($activated_levels as $level) {
17045:             foreach ($this->fixesForLevel[$level] as $fix) {
17046:                 $ret[$fix] = true;
17047:             }
17048:         }
17049:         return $ret;
17050:     }
17051: 
17052:     17053: 17054: 17055: 17056: 17057: 
17058:     public function makeFixesForLevel($fixes)
17059:     {
17060:         if (!isset($this->defaultLevel)) {
17061:             return;
17062:         }
17063:         if (!isset($this->fixesForLevel[$this->defaultLevel])) {
17064:             trigger_error(
17065:                 'Default level ' . $this->defaultLevel . ' does not exist',
17066:                 E_USER_ERROR
17067:             );
17068:             return;
17069:         }
17070:         $this->fixesForLevel[$this->defaultLevel] = array_keys($fixes);
17071:     }
17072: 
17073:     17074: 17075: 17076: 17077: 
17078:     public function populate($fixes)
17079:     {
17080:         foreach ($fixes as $name => $fix) {
17081:             
17082:             list($type, $params) = $this->getFixType($name);
17083:             switch ($type) {
17084:                 case 'attr_transform_pre':
17085:                 case 'attr_transform_post':
17086:                     $attr = $params['attr'];
17087:                     if (isset($params['element'])) {
17088:                         $element = $params['element'];
17089:                         if (empty($this->info[$element])) {
17090:                             $e = $this->addBlankElement($element);
17091:                         } else {
17092:                             $e = $this->info[$element];
17093:                         }
17094:                     } else {
17095:                         $type = "info_$type";
17096:                         $e = $this;
17097:                     }
17098:                     
17099:                     
17100:                     $f =& $e->$type;
17101:                     $f[$attr] = $fix;
17102:                     break;
17103:                 case 'tag_transform':
17104:                     $this->info_tag_transform[$params['element']] = $fix;
17105:                     break;
17106:                 case 'child':
17107:                 case 'content_model_type':
17108:                     $element = $params['element'];
17109:                     if (empty($this->info[$element])) {
17110:                         $e = $this->addBlankElement($element);
17111:                     } else {
17112:                         $e = $this->info[$element];
17113:                     }
17114:                     $e->$type = $fix;
17115:                     break;
17116:                 default:
17117:                     trigger_error("Fix type $type not supported", E_USER_ERROR);
17118:                     break;
17119:             }
17120:         }
17121:     }
17122: 
17123:     17124: 17125: 17126: 17127: 17128: 17129: 17130: 
17131:     public function getFixType($name)
17132:     {
17133:         
17134:         $property = $attr = null;
17135:         if (strpos($name, '#') !== false) {
17136:             list($name, $property) = explode('#', $name);
17137:         }
17138:         if (strpos($name, '@') !== false) {
17139:             list($name, $attr) = explode('@', $name);
17140:         }
17141: 
17142:         
17143:         $params = array();
17144:         if ($name !== '') {
17145:             $params['element'] = $name;
17146:         }
17147:         if (!is_null($attr)) {
17148:             $params['attr'] = $attr;
17149:         }
17150: 
17151:         
17152:         if (!is_null($attr)) {
17153:             if (is_null($property)) {
17154:                 $property = 'pre';
17155:             }
17156:             $type = 'attr_transform_' . $property;
17157:             return array($type, $params);
17158:         }
17159: 
17160:         
17161:         if (is_null($property)) {
17162:             return array('tag_transform', $params);
17163:         }
17164: 
17165:         return array($property, $params);
17166: 
17167:     }
17168: 
17169:     17170: 17171: 17172: 17173: 
17174:     public function makeFixes()
17175:     {
17176:     }
17177: }
17178: 
17179: 
17180: 
17181: 
17182: 
17183: class HTMLPurifier_HTMLModule_XMLCommonAttributes extends HTMLPurifier_HTMLModule
17184: {
17185:     17186: 17187: 
17188:     public $name = 'XMLCommonAttributes';
17189: 
17190:     17191: 17192: 
17193:     public $attr_collections = array(
17194:         'Lang' => array(
17195:             'xml:lang' => 'LanguageCode',
17196:         )
17197:     );
17198: }
17199: 
17200: 
17201: 
17202: 
17203: 
17204: 17205: 17206: 
17207: class HTMLPurifier_HTMLModule_Tidy_Name extends HTMLPurifier_HTMLModule_Tidy
17208: {
17209:     17210: 17211: 
17212:     public $name = 'Tidy_Name';
17213: 
17214:     17215: 17216: 
17217:     public $defaultLevel = 'heavy';
17218: 
17219:     17220: 17221: 
17222:     public function makeFixes()
17223:     {
17224:         $r = array();
17225:         
17226:         
17227:         
17228:         $r['img@name'] =
17229:         $r['a@name'] = new HTMLPurifier_AttrTransform_Name();
17230:         return $r;
17231:     }
17232: }
17233: 
17234: 
17235: 
17236: 
17237: 
17238: class HTMLPurifier_HTMLModule_Tidy_Proprietary extends HTMLPurifier_HTMLModule_Tidy
17239: {
17240: 
17241:     17242: 17243: 
17244:     public $name = 'Tidy_Proprietary';
17245: 
17246:     17247: 17248: 
17249:     public $defaultLevel = 'light';
17250: 
17251:     17252: 17253: 
17254:     public function makeFixes()
17255:     {
17256:         $r = array();
17257:         $r['table@background'] = new HTMLPurifier_AttrTransform_Background();
17258:         $r['td@background']    = new HTMLPurifier_AttrTransform_Background();
17259:         $r['th@background']    = new HTMLPurifier_AttrTransform_Background();
17260:         $r['tr@background']    = new HTMLPurifier_AttrTransform_Background();
17261:         $r['thead@background'] = new HTMLPurifier_AttrTransform_Background();
17262:         $r['tfoot@background'] = new HTMLPurifier_AttrTransform_Background();
17263:         $r['tbody@background'] = new HTMLPurifier_AttrTransform_Background();
17264:         $r['table@height']     = new HTMLPurifier_AttrTransform_Length('height');
17265:         return $r;
17266:     }
17267: }
17268: 
17269: 
17270: 
17271: 
17272: 
17273: class HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4 extends HTMLPurifier_HTMLModule_Tidy
17274: {
17275: 
17276:     17277: 17278: 
17279:     public function makeFixes()
17280:     {
17281:         $r = array();
17282: 
17283:         
17284: 
17285:         $r['font'] = new HTMLPurifier_TagTransform_Font();
17286:         $r['menu'] = new HTMLPurifier_TagTransform_Simple('ul');
17287:         $r['dir'] = new HTMLPurifier_TagTransform_Simple('ul');
17288:         $r['center'] = new HTMLPurifier_TagTransform_Simple('div', 'text-align:center;');
17289:         $r['u'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:underline;');
17290:         $r['s'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;');
17291:         $r['strike'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;');
17292: 
17293:         
17294: 
17295:         $r['caption@align'] =
17296:             new HTMLPurifier_AttrTransform_EnumToCSS(
17297:                 'align',
17298:                 array(
17299:                     
17300:                     
17301:                     
17302:                     
17303:                     'left' => 'text-align:left;',
17304:                     'right' => 'text-align:right;',
17305:                     'top' => 'caption-side:top;',
17306:                     'bottom' => 'caption-side:bottom;' 
17307:                 )
17308:             );
17309: 
17310:         
17311:         $r['img@align'] =
17312:             new HTMLPurifier_AttrTransform_EnumToCSS(
17313:                 'align',
17314:                 array(
17315:                     'left' => 'float:left;',
17316:                     'right' => 'float:right;',
17317:                     'top' => 'vertical-align:top;',
17318:                     'middle' => 'vertical-align:middle;',
17319:                     'bottom' => 'vertical-align:baseline;',
17320:                 )
17321:             );
17322: 
17323:         
17324:         $r['table@align'] =
17325:             new HTMLPurifier_AttrTransform_EnumToCSS(
17326:                 'align',
17327:                 array(
17328:                     'left' => 'float:left;',
17329:                     'center' => 'margin-left:auto;margin-right:auto;',
17330:                     'right' => 'float:right;'
17331:                 )
17332:             );
17333: 
17334:         
17335:         $r['hr@align'] =
17336:             new HTMLPurifier_AttrTransform_EnumToCSS(
17337:                 'align',
17338:                 array(
17339:                     
17340:                     
17341:                     
17342:                     
17343:                     'left' => 'margin-left:0;margin-right:auto;text-align:left;',
17344:                     'center' => 'margin-left:auto;margin-right:auto;text-align:center;',
17345:                     'right' => 'margin-left:auto;margin-right:0;text-align:right;'
17346:                 )
17347:             );
17348: 
17349:         
17350:         
17351:         $align_lookup = array();
17352:         $align_values = array('left', 'right', 'center', 'justify');
17353:         foreach ($align_values as $v) {
17354:             $align_lookup[$v] = "text-align:$v;";
17355:         }
17356:         
17357:         $r['h1@align'] =
17358:         $r['h2@align'] =
17359:         $r['h3@align'] =
17360:         $r['h4@align'] =
17361:         $r['h5@align'] =
17362:         $r['h6@align'] =
17363:         $r['p@align'] =
17364:         $r['div@align'] =
17365:             new HTMLPurifier_AttrTransform_EnumToCSS('align', $align_lookup);
17366: 
17367:         
17368:         $r['table@bgcolor'] =
17369:         $r['td@bgcolor'] =
17370:         $r['th@bgcolor'] =
17371:             new HTMLPurifier_AttrTransform_BgColor();
17372: 
17373:         
17374:         $r['img@border'] = new HTMLPurifier_AttrTransform_Border();
17375: 
17376:         
17377:         $r['br@clear'] =
17378:             new HTMLPurifier_AttrTransform_EnumToCSS(
17379:                 'clear',
17380:                 array(
17381:                     'left' => 'clear:left;',
17382:                     'right' => 'clear:right;',
17383:                     'all' => 'clear:both;',
17384:                     'none' => 'clear:none;',
17385:                 )
17386:             );
17387: 
17388:         
17389:         $r['td@height'] =
17390:         $r['th@height'] =
17391:             new HTMLPurifier_AttrTransform_Length('height');
17392: 
17393:         
17394:         $r['img@hspace'] = new HTMLPurifier_AttrTransform_ImgSpace('hspace');
17395: 
17396:         
17397:         
17398:         
17399:         $r['hr@noshade'] =
17400:             new HTMLPurifier_AttrTransform_BoolToCSS(
17401:                 'noshade',
17402:                 'color:#808080;background-color:#808080;border:0;'
17403:             );
17404: 
17405:         
17406:         $r['td@nowrap'] =
17407:         $r['th@nowrap'] =
17408:             new HTMLPurifier_AttrTransform_BoolToCSS(
17409:                 'nowrap',
17410:                 'white-space:nowrap;'
17411:             );
17412: 
17413:         
17414:         $r['hr@size'] = new HTMLPurifier_AttrTransform_Length('size', 'height');
17415: 
17416:         
17417:         
17418:         $ul_types = array(
17419:             'disc' => 'list-style-type:disc;',
17420:             'square' => 'list-style-type:square;',
17421:             'circle' => 'list-style-type:circle;'
17422:         );
17423:         $ol_types = array(
17424:             '1' => 'list-style-type:decimal;',
17425:             'i' => 'list-style-type:lower-roman;',
17426:             'I' => 'list-style-type:upper-roman;',
17427:             'a' => 'list-style-type:lower-alpha;',
17428:             'A' => 'list-style-type:upper-alpha;'
17429:         );
17430:         $li_types = $ul_types + $ol_types;
17431:         
17432: 
17433:         $r['ul@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ul_types);
17434:         $r['ol@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ol_types, true);
17435:         $r['li@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $li_types, true);
17436: 
17437:         
17438:         $r['img@vspace'] = new HTMLPurifier_AttrTransform_ImgSpace('vspace');
17439: 
17440:         
17441:         $r['td@width'] =
17442:         $r['th@width'] =
17443:         $r['hr@width'] = new HTMLPurifier_AttrTransform_Length('width');
17444: 
17445:         return $r;
17446:     }
17447: }
17448: 
17449: 
17450: 
17451: 
17452: 
17453: class HTMLPurifier_HTMLModule_Tidy_Strict extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4
17454: {
17455:     17456: 17457: 
17458:     public $name = 'Tidy_Strict';
17459: 
17460:     17461: 17462: 
17463:     public $defaultLevel = 'light';
17464: 
17465:     17466: 17467: 
17468:     public function makeFixes()
17469:     {
17470:         $r = parent::makeFixes();
17471:         $r['blockquote#content_model_type'] = 'strictblockquote';
17472:         return $r;
17473:     }
17474: 
17475:     17476: 17477: 
17478:     public $defines_child_def = true;
17479: 
17480:     17481: 17482: 17483: 
17484:     public function getChildDef($def)
17485:     {
17486:         if ($def->content_model_type != 'strictblockquote') {
17487:             return parent::getChildDef($def);
17488:         }
17489:         return new HTMLPurifier_ChildDef_StrictBlockquote($def->content_model);
17490:     }
17491: }
17492: 
17493: 
17494: 
17495: 
17496: 
17497: class HTMLPurifier_HTMLModule_Tidy_Transitional extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4
17498: {
17499:     17500: 17501: 
17502:     public $name = 'Tidy_Transitional';
17503: 
17504:     17505: 17506: 
17507:     public $defaultLevel = 'heavy';
17508: }
17509: 
17510: 
17511: 
17512: 
17513: 
17514: class HTMLPurifier_HTMLModule_Tidy_XHTML extends HTMLPurifier_HTMLModule_Tidy
17515: {
17516:     17517: 17518: 
17519:     public $name = 'Tidy_XHTML';
17520: 
17521:     17522: 17523: 
17524:     public $defaultLevel = 'medium';
17525: 
17526:     17527: 17528: 
17529:     public function makeFixes()
17530:     {
17531:         $r = array();
17532:         $r['@lang'] = new HTMLPurifier_AttrTransform_Lang();
17533:         return $r;
17534:     }
17535: }
17536: 
17537: 
17538: 
17539: 
17540: 
17541: 17542: 17543: 17544: 17545: 17546: 
17547: class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector
17548: {
17549:     17550: 17551: 
17552:     public $name = 'AutoParagraph';
17553: 
17554:     17555: 17556: 
17557:     public $needed = array('p');
17558: 
17559:     17560: 17561: 
17562:     private function _pStart()
17563:     {
17564:         $par = new HTMLPurifier_Token_Start('p');
17565:         $par->armor['MakeWellFormed_TagClosedError'] = true;
17566:         return $par;
17567:     }
17568: 
17569:     17570: 17571: 
17572:     public function handleText(&$token)
17573:     {
17574:         $text = $token->data;
17575:         
17576:         if ($this->allowsElement('p')) {
17577:             if (empty($this->currentNesting) || strpos($text, "\n\n") !== false) {
17578:                 
17579:                 
17580:                 
17581:                 
17582: 
17583:                 $i = $nesting = null;
17584:                 if (!$this->forwardUntilEndToken($i, $current, $nesting) && $token->is_whitespace) {
17585:                     
17586:                     
17587:                     
17588:                 } else {
17589:                     if (!$token->is_whitespace || $this->_isInline($current)) {
17590:                         
17591:                         
17592: 
17593:                         
17594:                         
17595: 
17596:                         
17597:                         
17598:                         $token = array($this->_pStart());
17599:                         $this->_splitText($text, $token);
17600:                     } else {
17601:                         
17602:                         
17603:                     }
17604:                 }
17605:             } else {
17606:                 
17607:                 
17608: 
17609:                 
17610:                 
17611:                 if ($this->_pLookAhead()) {
17612:                     
17613:                     
17614:                     
17615:                     
17616:                     
17617:                     
17618:                     $token = array($this->_pStart(), $token);
17619:                 } else {
17620:                     
17621:                     
17622: 
17623:                     
17624:                     
17625:                 }
17626:             }
17627:             
17628:         } elseif (!empty($this->currentNesting) &&
17629:             $this->currentNesting[count($this->currentNesting) - 1]->name == 'p') {
17630:             
17631:             
17632: 
17633:             
17634:             
17635:             $token = array();
17636:             $this->_splitText($text, $token);
17637:             
17638:         } else {
17639:             
17640:             
17641: 
17642:             
17643:             
17644:         }
17645:     }
17646: 
17647:     17648: 17649: 
17650:     public function handleElement(&$token)
17651:     {
17652:         
17653:         
17654:         if ($this->allowsElement('p')) {
17655:             if (!empty($this->currentNesting)) {
17656:                 if ($this->_isInline($token)) {
17657:                     
17658:                     
17659:                     
17660:                     
17661:                     $i = null;
17662:                     $this->backward($i, $prev);
17663: 
17664:                     if (!$prev instanceof HTMLPurifier_Token_Start) {
17665:                         
17666:                         if ($prev instanceof HTMLPurifier_Token_Text &&
17667:                             substr($prev->data, -2) === "\n\n"
17668:                         ) {
17669:                             
17670:                             
17671:                             
17672:                             $token = array($this->_pStart(), $token);
17673:                         } else {
17674:                             
17675:                             
17676:                             
17677:                             
17678:                             
17679:                             
17680:                         }
17681:                     } else {
17682:                         
17683:                         
17684:                         
17685:                         if ($this->_pLookAhead()) {
17686:                             
17687:                             
17688:                             $token = array($this->_pStart(), $token);
17689:                         } else {
17690:                             
17691:                             
17692: 
17693:                             
17694:                             
17695:                         }
17696:                     }
17697:                 } else {
17698:                     
17699:                     
17700:                 }
17701:             } else {
17702:                 if ($this->_isInline($token)) {
17703:                     
17704:                     
17705:                     
17706:                     
17707:                     $token = array($this->_pStart(), $token);
17708:                 } else {
17709:                     
17710:                     
17711:                 }
17712: 
17713:                 $i = null;
17714:                 if ($this->backward($i, $prev)) {
17715:                     if (!$prev instanceof HTMLPurifier_Token_Text) {
17716:                         
17717:                         
17718:                         
17719:                         
17720:                         if (!is_array($token)) {
17721:                             $token = array($token);
17722:                         }
17723:                         array_unshift($token, new HTMLPurifier_Token_Text("\n\n"));
17724:                     } else {
17725:                         
17726:                         
17727:                         
17728:                         
17729:                         
17730:                         
17731:                     }
17732:                 }
17733:             }
17734:         } else {
17735:             
17736:             
17737:             
17738:             
17739:         }
17740:     }
17741: 
17742:     17743: 17744: 17745: 17746: 17747: 17748: 17749: 
17750:     private function _splitText($data, &$result)
17751:     {
17752:         $raw_paragraphs = explode("\n\n", $data);
17753:         $paragraphs = array(); 
17754:         $needs_start = false;
17755:         $needs_end = false;
17756: 
17757:         $c = count($raw_paragraphs);
17758:         if ($c == 1) {
17759:             
17760:             
17761:             $result[] = new HTMLPurifier_Token_Text($data);
17762:             return;
17763:         }
17764:         for ($i = 0; $i < $c; $i++) {
17765:             $par = $raw_paragraphs[$i];
17766:             if (trim($par) !== '') {
17767:                 $paragraphs[] = $par;
17768:             } else {
17769:                 if ($i == 0) {
17770:                     
17771:                     if (empty($result)) {
17772:                         
17773:                         
17774:                         
17775:                         
17776:                         $result[] = new HTMLPurifier_Token_End('p');
17777:                         $result[] = new HTMLPurifier_Token_Text("\n\n");
17778:                         
17779:                         
17780:                         
17781:                         
17782:                         
17783:                         $needs_start = true;
17784:                     } else {
17785:                         
17786:                         
17787:                         
17788:                         array_unshift($result, new HTMLPurifier_Token_Text("\n\n"));
17789:                     }
17790:                 } elseif ($i + 1 == $c) {
17791:                     
17792:                     
17793:                     $needs_end = true;
17794:                 }
17795:             }
17796:         }
17797: 
17798:         
17799:         
17800:         if (empty($paragraphs)) {
17801:             return;
17802:         }
17803: 
17804:         
17805:         if ($needs_start) {
17806:             $result[] = $this->_pStart();
17807:         }
17808: 
17809:         
17810:         foreach ($paragraphs as $par) {
17811:             $result[] = new HTMLPurifier_Token_Text($par);
17812:             $result[] = new HTMLPurifier_Token_End('p');
17813:             $result[] = new HTMLPurifier_Token_Text("\n\n");
17814:             $result[] = $this->_pStart();
17815:         }
17816: 
17817:         
17818:         
17819:         
17820:         array_pop($result);
17821: 
17822:         
17823:         
17824:         if (!$needs_end) {
17825:             array_pop($result); 
17826:             array_pop($result); 
17827:         }
17828:     }
17829: 
17830:     17831: 17832: 17833: 17834: 17835: 
17836:     private function _isInline($token)
17837:     {
17838:         return isset($this->htmlDefinition->info['p']->child->elements[$token->name]);
17839:     }
17840: 
17841:     17842: 17843: 17844: 17845: 
17846:     private function _pLookAhead()
17847:     {
17848:         if ($this->currentToken instanceof HTMLPurifier_Token_Start) {
17849:             $nesting = 1;
17850:         } else {
17851:             $nesting = 0;
17852:         }
17853:         $ok = false;
17854:         $i = null;
17855:         while ($this->forwardUntilEndToken($i, $current, $nesting)) {
17856:             $result = $this->_checkNeedsP($current);
17857:             if ($result !== null) {
17858:                 $ok = $result;
17859:                 break;
17860:             }
17861:         }
17862:         return $ok;
17863:     }
17864: 
17865:     17866: 17867: 17868: 17869: 17870: 
17871:     private function _checkNeedsP($current)
17872:     {
17873:         if ($current instanceof HTMLPurifier_Token_Start) {
17874:             if (!$this->_isInline($current)) {
17875:                 
17876:                 
17877:                 
17878:                 return false;
17879:             }
17880:         } elseif ($current instanceof HTMLPurifier_Token_Text) {
17881:             if (strpos($current->data, "\n\n") !== false) {
17882:                 
17883:                 
17884:                 return true;
17885:             } else {
17886:                 
17887:                 
17888:             }
17889:         }
17890:         return null;
17891:     }
17892: }
17893: 
17894: 
17895: 
17896: 
17897: 
17898: 17899: 17900: 
17901: class HTMLPurifier_Injector_DisplayLinkURI extends HTMLPurifier_Injector
17902: {
17903:     17904: 17905: 
17906:     public $name = 'DisplayLinkURI';
17907: 
17908:     17909: 17910: 
17911:     public $needed = array('a');
17912: 
17913:     17914: 17915: 
17916:     public function handleElement(&$token)
17917:     {
17918:     }
17919: 
17920:     17921: 17922: 
17923:     public function handleEnd(&$token)
17924:     {
17925:         if (isset($token->start->attr['href'])) {
17926:             $url = $token->start->attr['href'];
17927:             unset($token->start->attr['href']);
17928:             $token = array($token, new HTMLPurifier_Token_Text(" ($url)"));
17929:         } else {
17930:             
17931:         }
17932:     }
17933: }
17934: 
17935: 
17936: 
17937: 
17938: 
17939: 17940: 17941: 
17942: class HTMLPurifier_Injector_Linkify extends HTMLPurifier_Injector
17943: {
17944:     17945: 17946: 
17947:     public $name = 'Linkify';
17948: 
17949:     17950: 17951: 
17952:     public $needed = array('a' => array('href'));
17953: 
17954:     17955: 17956: 
17957:     public function handleText(&$token)
17958:     {
17959:         if (!$this->allowsElement('a')) {
17960:             return;
17961:         }
17962: 
17963:         if (strpos($token->data, '://') === false) {
17964:             
17965:             
17966:             
17967:             return;
17968:         }
17969: 
17970:         
17971:         
17972:         $bits = preg_split('#((?:https?|ftp)://[^\s\'",<>()]+)#Su', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE);
17973: 
17974: 
17975:         $token = array();
17976: 
17977:         
17978:         
17979:         
17980:         for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) {
17981:             if (!$l) {
17982:                 if ($bits[$i] === '') {
17983:                     continue;
17984:                 }
17985:                 $token[] = new HTMLPurifier_Token_Text($bits[$i]);
17986:             } else {
17987:                 $token[] = new HTMLPurifier_Token_Start('a', array('href' => $bits[$i]));
17988:                 $token[] = new HTMLPurifier_Token_Text($bits[$i]);
17989:                 $token[] = new HTMLPurifier_Token_End('a');
17990:             }
17991:         }
17992:     }
17993: }
17994: 
17995: 
17996: 
17997: 
17998: 
17999: 18000: 18001: 18002: 
18003: class HTMLPurifier_Injector_PurifierLinkify extends HTMLPurifier_Injector
18004: {
18005:     18006: 18007: 
18008:     public $name = 'PurifierLinkify';
18009: 
18010:     18011: 18012: 
18013:     public $docURL;
18014: 
18015:     18016: 18017: 
18018:     public $needed = array('a' => array('href'));
18019: 
18020:     18021: 18022: 18023: 18024: 
18025:     public function prepare($config, $context)
18026:     {
18027:         $this->docURL = $config->get('AutoFormat.PurifierLinkify.DocURL');
18028:         return parent::prepare($config, $context);
18029:     }
18030: 
18031:     18032: 18033: 
18034:     public function handleText(&$token)
18035:     {
18036:         if (!$this->allowsElement('a')) {
18037:             return;
18038:         }
18039:         if (strpos($token->data, '%') === false) {
18040:             return;
18041:         }
18042: 
18043:         $bits = preg_split('#%([a-z0-9]+\.[a-z0-9]+)#Si', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE);
18044:         $token = array();
18045: 
18046:         
18047:         
18048:         
18049:         for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) {
18050:             if (!$l) {
18051:                 if ($bits[$i] === '') {
18052:                     continue;
18053:                 }
18054:                 $token[] = new HTMLPurifier_Token_Text($bits[$i]);
18055:             } else {
18056:                 $token[] = new HTMLPurifier_Token_Start(
18057:                     'a',
18058:                     array('href' => str_replace('%s', $bits[$i], $this->docURL))
18059:                 );
18060:                 $token[] = new HTMLPurifier_Token_Text('%' . $bits[$i]);
18061:                 $token[] = new HTMLPurifier_Token_End('a');
18062:             }
18063:         }
18064:     }
18065: }
18066: 
18067: 
18068: 
18069: 
18070: 
18071: class HTMLPurifier_Injector_RemoveEmpty extends HTMLPurifier_Injector
18072: {
18073:     18074: 18075: 
18076:     private $context;
18077: 
18078:     18079: 18080: 
18081:     private $config;
18082: 
18083:     18084: 18085: 
18086:     private $attrValidator;
18087: 
18088:     18089: 18090: 
18091:     private $removeNbsp;
18092: 
18093:     18094: 18095: 
18096:     private $removeNbspExceptions;
18097: 
18098:     18099: 18100: 18101: 
18102:     private $_exclude = array('colgroup' => 1, 'th' => 1, 'td' => 1, 'iframe' => 1);
18103: 
18104:     18105: 18106: 18107: 18108: 
18109:     public function prepare($config, $context)
18110:     {
18111:         parent::prepare($config, $context);
18112:         $this->config = $config;
18113:         $this->context = $context;
18114:         $this->removeNbsp = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp');
18115:         $this->removeNbspExceptions = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions');
18116:         $this->attrValidator = new HTMLPurifier_AttrValidator();
18117:     }
18118: 
18119:     18120: 18121: 
18122:     public function handleElement(&$token)
18123:     {
18124:         if (!$token instanceof HTMLPurifier_Token_Start) {
18125:             return;
18126:         }
18127:         $next = false;
18128:         $deleted = 1; 
18129:         for ($i = count($this->inputZipper->back) - 1; $i >= 0; $i--, $deleted++) {
18130:             $next = $this->inputZipper->back[$i];
18131:             if ($next instanceof HTMLPurifier_Token_Text) {
18132:                 if ($next->is_whitespace) {
18133:                     continue;
18134:                 }
18135:                 if ($this->removeNbsp && !isset($this->removeNbspExceptions[$token->name])) {
18136:                     $plain = str_replace("\xC2\xA0", "", $next->data);
18137:                     $isWsOrNbsp = $plain === '' || ctype_space($plain);
18138:                     if ($isWsOrNbsp) {
18139:                         continue;
18140:                     }
18141:                 }
18142:             }
18143:             break;
18144:         }
18145:         if (!$next || ($next instanceof HTMLPurifier_Token_End && $next->name == $token->name)) {
18146:             if (isset($this->_exclude[$token->name])) {
18147:                 return;
18148:             }
18149:             $this->attrValidator->validateToken($token, $this->config, $this->context);
18150:             $token->armor['ValidateAttributes'] = true;
18151:             if (isset($token->attr['id']) || isset($token->attr['name'])) {
18152:                 return;
18153:             }
18154:             $token = $deleted + 1;
18155:             for ($b = 0, $c = count($this->inputZipper->front); $b < $c; $b++) {
18156:                 $prev = $this->inputZipper->front[$b];
18157:                 if ($prev instanceof HTMLPurifier_Token_Text && $prev->is_whitespace) {
18158:                     continue;
18159:                 }
18160:                 break;
18161:             }
18162:             
18163:             $this->rewindOffset($b+$deleted);
18164:             return;
18165:         }
18166:     }
18167: }
18168: 
18169: 
18170: 
18171: 
18172: 
18173: 18174: 18175: 
18176: class HTMLPurifier_Injector_RemoveSpansWithoutAttributes extends HTMLPurifier_Injector
18177: {
18178:     18179: 18180: 
18181:     public $name = 'RemoveSpansWithoutAttributes';
18182: 
18183:     18184: 18185: 
18186:     public $needed = array('span');
18187: 
18188:     18189: 18190: 
18191:     private $attrValidator;
18192: 
18193:     18194: 18195: 18196: 
18197:     private $config;
18198: 
18199:     18200: 18201: 
18202:     private $context;
18203: 
18204:     public function prepare($config, $context)
18205:     {
18206:         $this->attrValidator = new HTMLPurifier_AttrValidator();
18207:         $this->config = $config;
18208:         $this->context = $context;
18209:         return parent::prepare($config, $context);
18210:     }
18211: 
18212:     18213: 18214: 
18215:     public function handleElement(&$token)
18216:     {
18217:         if ($token->name !== 'span' || !$token instanceof HTMLPurifier_Token_Start) {
18218:             return;
18219:         }
18220: 
18221:         
18222:         
18223:         
18224:         $this->attrValidator->validateToken($token, $this->config, $this->context);
18225:         $token->armor['ValidateAttributes'] = true;
18226: 
18227:         if (!empty($token->attr)) {
18228:             return;
18229:         }
18230: 
18231:         $nesting = 0;
18232:         while ($this->forwardUntilEndToken($i, $current, $nesting)) {
18233:         }
18234: 
18235:         if ($current instanceof HTMLPurifier_Token_End && $current->name === 'span') {
18236:             
18237:             $current->markForDeletion = true;
18238:             
18239:             $token = false;
18240:         }
18241:     }
18242: 
18243:     18244: 18245: 
18246:     public function handleEnd(&$token)
18247:     {
18248:         if ($token->markForDeletion) {
18249:             $token = false;
18250:         }
18251:     }
18252: }
18253: 
18254: 
18255: 
18256: 
18257: 
18258: 18259: 18260: 18261: 
18262: class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector
18263: {
18264:     18265: 18266: 
18267:     public $name = 'SafeObject';
18268: 
18269:     18270: 18271: 
18272:     public $needed = array('object', 'param');
18273: 
18274:     18275: 18276: 
18277:     protected $objectStack = array();
18278: 
18279:     18280: 18281: 
18282:     protected $paramStack = array();
18283: 
18284:     18285: 18286: 18287: 
18288:     protected $addParam = array(
18289:         'allowScriptAccess' => 'never',
18290:         'allowNetworking' => 'internal',
18291:     );
18292: 
18293:     18294: 18295: 
18296:     protected $allowedParam = array(
18297:         'wmode' => true,
18298:         'movie' => true,
18299:         'flashvars' => true,
18300:         'src' => true,
18301:         'allowFullScreen' => true, 
18302:     );
18303: 
18304:     18305: 18306: 18307: 18308: 
18309:     public function prepare($config, $context)
18310:     {
18311:         parent::prepare($config, $context);
18312:     }
18313: 
18314:     18315: 18316: 
18317:     public function handleElement(&$token)
18318:     {
18319:         if ($token->name == 'object') {
18320:             $this->objectStack[] = $token;
18321:             $this->paramStack[] = array();
18322:             $new = array($token);
18323:             foreach ($this->addParam as $name => $value) {
18324:                 $new[] = new HTMLPurifier_Token_Empty('param', array('name' => $name, 'value' => $value));
18325:             }
18326:             $token = $new;
18327:         } elseif ($token->name == 'param') {
18328:             $nest = count($this->currentNesting) - 1;
18329:             if ($nest >= 0 && $this->currentNesting[$nest]->name === 'object') {
18330:                 $i = count($this->objectStack) - 1;
18331:                 if (!isset($token->attr['name'])) {
18332:                     $token = false;
18333:                     return;
18334:                 }
18335:                 $n = $token->attr['name'];
18336:                 
18337:                 
18338:                 
18339:                 if (!isset($this->objectStack[$i]->attr['data']) &&
18340:                     ($token->attr['name'] == 'movie' || $token->attr['name'] == 'src')
18341:                 ) {
18342:                     $this->objectStack[$i]->attr['data'] = $token->attr['value'];
18343:                 }
18344:                 
18345:                 
18346:                 if (!isset($this->paramStack[$i][$n]) &&
18347:                     isset($this->addParam[$n]) &&
18348:                     $token->attr['name'] === $this->addParam[$n]) {
18349:                     
18350:                     $this->paramStack[$i][$n] = true;
18351:                 } elseif (isset($this->allowedParam[$n])) {
18352:                     
18353:                     
18354:                 } else {
18355:                     $token = false;
18356:                 }
18357:             } else {
18358:                 
18359:                 $token = false;
18360:             }
18361:         }
18362:     }
18363: 
18364:     public function handleEnd(&$token)
18365:     {
18366:         
18367:         
18368:         
18369:         if ($token->name == 'object') {
18370:             array_pop($this->objectStack);
18371:             array_pop($this->paramStack);
18372:         }
18373:     }
18374: }
18375: 
18376: 
18377: 
18378: 
18379: 
18380: 18381: 18382: 18383: 18384: 18385: 18386: 18387: 18388: 18389: 18390: 18391: 18392: 18393: 18394: 18395: 18396: 18397: 18398: 18399: 18400: 18401: 18402: 
18403: 
18404: class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
18405: {
18406: 
18407:     18408: 18409: 
18410:     private $factory;
18411: 
18412:     public function __construct()
18413:     {
18414:         
18415:         parent::__construct();
18416:         $this->factory = new HTMLPurifier_TokenFactory();
18417:     }
18418: 
18419:     18420: 18421: 18422: 18423: 18424: 
18425:     public function tokenizeHTML($html, $config, $context)
18426:     {
18427:         $html = $this->normalize($html, $config, $context);
18428: 
18429:         
18430:         
18431:         if ($config->get('Core.AggressivelyFixLt')) {
18432:             $char = '[^a-z!\/]';
18433:             $comment = "/<!--(.*?)(-->|\z)/is";
18434:             $html = preg_replace_callback($comment, array($this, 'callbackArmorCommentEntities'), $html);
18435:             do {
18436:                 $old = $html;
18437:                 $html = preg_replace("/<($char)/i", '<\\1', $html);
18438:             } while ($html !== $old);
18439:             $html = preg_replace_callback($comment, array($this, 'callbackUndoCommentSubst'), $html); 
18440:         }
18441: 
18442:         
18443:         $html = $this->wrapHTML($html, $config, $context);
18444: 
18445:         $doc = new DOMDocument();
18446:         $doc->encoding = 'UTF-8'; 
18447: 
18448:         set_error_handler(array($this, 'muteErrorHandler'));
18449:         $doc->loadHTML($html);
18450:         restore_error_handler();
18451: 
18452:         $tokens = array();
18453:         $this->tokenizeDOM(
18454:             $doc->getElementsByTagName('html')->item(0)-> 
18455:             getElementsByTagName('body')->item(0)-> 
18456:             getElementsByTagName('div')->item(0), 
18457:             $tokens
18458:         );
18459:         return $tokens;
18460:     }
18461: 
18462:     18463: 18464: 18465: 18466: 18467: 18468: 
18469:     protected function tokenizeDOM($node, &$tokens)
18470:     {
18471:         $level = 0;
18472:         $nodes = array($level => new HTMLPurifier_Queue(array($node)));
18473:         $closingNodes = array();
18474:         do {
18475:             while (!$nodes[$level]->isEmpty()) {
18476:                 $node = $nodes[$level]->shift(); 
18477:                 $collect = $level > 0 ? true : false;
18478:                 $needEndingTag = $this->createStartNode($node, $tokens, $collect);
18479:                 if ($needEndingTag) {
18480:                     $closingNodes[$level][] = $node;
18481:                 }
18482:                 if ($node->childNodes && $node->childNodes->length) {
18483:                     $level++;
18484:                     $nodes[$level] = new HTMLPurifier_Queue();
18485:                     foreach ($node->childNodes as $childNode) {
18486:                         $nodes[$level]->push($childNode);
18487:                     }
18488:                 }
18489:             }
18490:             $level--;
18491:             if ($level && isset($closingNodes[$level])) {
18492:                 while ($node = array_pop($closingNodes[$level])) {
18493:                     $this->createEndNode($node, $tokens);
18494:                 }
18495:             }
18496:         } while ($level > 0);
18497:     }
18498: 
18499:     18500: 18501: 18502: 18503: 18504: 18505: 18506: 18507: 
18508:     protected function createStartNode($node, &$tokens, $collect)
18509:     {
18510:         
18511:         
18512:         
18513:         if ($node->nodeType === XML_TEXT_NODE) {
18514:             $tokens[] = $this->factory->createText($node->data);
18515:             return false;
18516:         } elseif ($node->nodeType === XML_CDATA_SECTION_NODE) {
18517:             
18518:             $last = end($tokens);
18519:             $data = $node->data;
18520:             
18521:             if ($last instanceof HTMLPurifier_Token_Start && ($last->name == 'script' || $last->name == 'style')) {
18522:                 $new_data = trim($data);
18523:                 if (substr($new_data, 0, 4) === '<!--') {
18524:                     $data = substr($new_data, 4);
18525:                     if (substr($data, -3) === '-->') {
18526:                         $data = substr($data, 0, -3);
18527:                     } else {
18528:                         
18529:                     }
18530:                 }
18531:             }
18532:             $tokens[] = $this->factory->createText($this->parseData($data));
18533:             return false;
18534:         } elseif ($node->nodeType === XML_COMMENT_NODE) {
18535:             
18536:             
18537:             
18538:             $tokens[] = $this->factory->createComment($node->data);
18539:             return false;
18540:         } elseif ($node->nodeType !== XML_ELEMENT_NODE) {
18541:             
18542:             return false;
18543:         }
18544: 
18545:         $attr = $node->hasAttributes() ? $this->transformAttrToAssoc($node->attributes) : array();
18546: 
18547:         
18548:         if (!$node->childNodes->length) {
18549:             if ($collect) {
18550:                 $tokens[] = $this->factory->createEmpty($node->tagName, $attr);
18551:             }
18552:             return false;
18553:         } else {
18554:             if ($collect) {
18555:                 $tokens[] = $this->factory->createStart(
18556:                     $tag_name = $node->tagName, 
18557:                     $attr
18558:                 );
18559:             }
18560:             return true;
18561:         }
18562:     }
18563: 
18564:     18565: 18566: 18567: 
18568:     protected function createEndNode($node, &$tokens)
18569:     {
18570:         $tokens[] = $this->factory->createEnd($node->tagName);
18571:     }
18572: 
18573: 
18574:     18575: 18576: 18577: 18578: 18579: 
18580:     protected function transformAttrToAssoc($node_map)
18581:     {
18582:         
18583:         
18584:         
18585:         if ($node_map->length === 0) {
18586:             return array();
18587:         }
18588:         $array = array();
18589:         foreach ($node_map as $attr) {
18590:             $array[$attr->name] = $attr->value;
18591:         }
18592:         return $array;
18593:     }
18594: 
18595:     18596: 18597: 18598: 18599: 
18600:     public function muteErrorHandler($errno, $errstr)
18601:     {
18602:     }
18603: 
18604:     18605: 18606: 18607: 18608: 18609: 
18610:     public function ($matches)
18611:     {
18612:         return '<!--' . strtr($matches[1], array('&' => '&', '<' => '<')) . $matches[2];
18613:     }
18614: 
18615:     18616: 18617: 18618: 18619: 18620: 
18621:     public function ($matches)
18622:     {
18623:         return '<!--' . str_replace('&', '&', $matches[1]) . $matches[2];
18624:     }
18625: 
18626:     18627: 18628: 18629: 18630: 18631: 18632: 
18633:     protected function wrapHTML($html, $config, $context)
18634:     {
18635:         $def = $config->getDefinition('HTML');
18636:         $ret = '';
18637: 
18638:         if (!empty($def->doctype->dtdPublic) || !empty($def->doctype->dtdSystem)) {
18639:             $ret .= '<!DOCTYPE html ';
18640:             if (!empty($def->doctype->dtdPublic)) {
18641:                 $ret .= 'PUBLIC "' . $def->doctype->dtdPublic . '" ';
18642:             }
18643:             if (!empty($def->doctype->dtdSystem)) {
18644:                 $ret .= '"' . $def->doctype->dtdSystem . '" ';
18645:             }
18646:             $ret .= '>';
18647:         }
18648: 
18649:         $ret .= '<html><head>';
18650:         $ret .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
18651:         
18652:         $ret .= '</head><body><div>' . $html . '</div></body></html>';
18653:         return $ret;
18654:     }
18655: }
18656: 
18657: 
18658: 
18659: 
18660: 
18661: 18662: 18663: 18664: 18665: 18666: 18667: 18668: 18669: 18670: 
18671: class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
18672: {
18673:     18674: 18675: 
18676:     public $tracksLineNumbers = true;
18677: 
18678:     18679: 18680: 18681: 
18682:     protected $_whitespace = "\x20\x09\x0D\x0A";
18683: 
18684:     18685: 18686: 18687: 18688: 
18689:     protected function scriptCallback($matches)
18690:     {
18691:         return $matches[1] . htmlspecialchars($matches[2], ENT_COMPAT, 'UTF-8') . $matches[3];
18692:     }
18693: 
18694:     18695: 18696: 18697: 18698: 18699: 
18700:     public function tokenizeHTML($html, $config, $context)
18701:     {
18702:         
18703:         
18704:         
18705:         if ($config->get('HTML.Trusted')) {
18706:             $html = preg_replace_callback(
18707:                 '#(<script[^>]*>)(\s*[^<].+?)(</script>)#si',
18708:                 array($this, 'scriptCallback'),
18709:                 $html
18710:             );
18711:         }
18712: 
18713:         $html = $this->normalize($html, $config, $context);
18714: 
18715:         $cursor = 0; 
18716:         $inside_tag = false; 
18717:         $array = array(); 
18718: 
18719:         
18720:         $maintain_line_numbers = $config->get('Core.MaintainLineNumbers');
18721: 
18722:         if ($maintain_line_numbers === null) {
18723:             
18724:             
18725:             $maintain_line_numbers = $config->get('Core.CollectErrors');
18726:         }
18727: 
18728:         if ($maintain_line_numbers) {
18729:             $current_line = 1;
18730:             $current_col = 0;
18731:             $length = strlen($html);
18732:         } else {
18733:             $current_line = false;
18734:             $current_col = false;
18735:             $length = false;
18736:         }
18737:         $context->register('CurrentLine', $current_line);
18738:         $context->register('CurrentCol', $current_col);
18739:         $nl = "\n";
18740:         
18741:         
18742:         $synchronize_interval = $config->get('Core.DirectLexLineNumberSyncInterval');
18743: 
18744:         $e = false;
18745:         if ($config->get('Core.CollectErrors')) {
18746:             $e =& $context->get('ErrorCollector');
18747:         }
18748: 
18749:         
18750:         $loops = 0;
18751: 
18752:         while (++$loops) {
18753:             
18754:             
18755:             
18756: 
18757:             if ($maintain_line_numbers) {
18758:                 
18759:                 $rcursor = $cursor - (int)$inside_tag;
18760: 
18761:                 
18762:                 
18763:                 
18764:                 
18765:                 $nl_pos = strrpos($html, $nl, $rcursor - $length);
18766:                 $current_col = $rcursor - (is_bool($nl_pos) ? 0 : $nl_pos + 1);
18767: 
18768:                 
18769:                 if ($synchronize_interval && 
18770:                     $cursor > 0 && 
18771:                     $loops % $synchronize_interval === 0) { 
18772:                     $current_line = 1 + $this->substrCount($html, $nl, 0, $cursor);
18773:                 }
18774:             }
18775: 
18776:             $position_next_lt = strpos($html, '<', $cursor);
18777:             $position_next_gt = strpos($html, '>', $cursor);
18778: 
18779:             
18780:             
18781:             if ($position_next_lt === $cursor) {
18782:                 $inside_tag = true;
18783:                 $cursor++;
18784:             }
18785: 
18786:             if (!$inside_tag && $position_next_lt !== false) {
18787:                 
18788:                 $token = new
18789:                 HTMLPurifier_Token_Text(
18790:                     $this->parseData(
18791:                         substr(
18792:                             $html,
18793:                             $cursor,
18794:                             $position_next_lt - $cursor
18795:                         )
18796:                     )
18797:                 );
18798:                 if ($maintain_line_numbers) {
18799:                     $token->rawPosition($current_line, $current_col);
18800:                     $current_line += $this->substrCount($html, $nl, $cursor, $position_next_lt - $cursor);
18801:                 }
18802:                 $array[] = $token;
18803:                 $cursor = $position_next_lt + 1;
18804:                 $inside_tag = true;
18805:                 continue;
18806:             } elseif (!$inside_tag) {
18807:                 
18808:                 
18809:                 if ($cursor === strlen($html)) {
18810:                     break;
18811:                 }
18812:                 
18813:                 $token = new
18814:                 HTMLPurifier_Token_Text(
18815:                     $this->parseData(
18816:                         substr(
18817:                             $html,
18818:                             $cursor
18819:                         )
18820:                     )
18821:                 );
18822:                 if ($maintain_line_numbers) {
18823:                     $token->rawPosition($current_line, $current_col);
18824:                 }
18825:                 $array[] = $token;
18826:                 break;
18827:             } elseif ($inside_tag && $position_next_gt !== false) {
18828:                 
18829:                 
18830:                 $strlen_segment = $position_next_gt - $cursor;
18831: 
18832:                 if ($strlen_segment < 1) {
18833:                     
18834:                     $token = new HTMLPurifier_Token_Text('<');
18835:                     $cursor++;
18836:                     continue;
18837:                 }
18838: 
18839:                 $segment = substr($html, $cursor, $strlen_segment);
18840: 
18841:                 if ($segment === false) {
18842:                     
18843:                     
18844:                     break;
18845:                 }
18846: 
18847:                 
18848:                 if (substr($segment, 0, 3) === '!--') {
18849:                     
18850:                     $position_comment_end = strpos($html, '-->', $cursor);
18851:                     if ($position_comment_end === false) {
18852:                         
18853:                         
18854:                         
18855:                         if ($e) {
18856:                             $e->send(E_WARNING, 'Lexer: Unclosed comment');
18857:                         }
18858:                         $position_comment_end = strlen($html);
18859:                         $end = true;
18860:                     } else {
18861:                         $end = false;
18862:                     }
18863:                     $strlen_segment = $position_comment_end - $cursor;
18864:                     $segment = substr($html, $cursor, $strlen_segment);
18865:                     $token = new
18866:                     HTMLPurifier_Token_Comment(
18867:                         substr(
18868:                             $segment,
18869:                             3,
18870:                             $strlen_segment - 3
18871:                         )
18872:                     );
18873:                     if ($maintain_line_numbers) {
18874:                         $token->rawPosition($current_line, $current_col);
18875:                         $current_line += $this->substrCount($html, $nl, $cursor, $strlen_segment);
18876:                     }
18877:                     $array[] = $token;
18878:                     $cursor = $end ? $position_comment_end : $position_comment_end + 3;
18879:                     $inside_tag = false;
18880:                     continue;
18881:                 }
18882: 
18883:                 
18884:                 $is_end_tag = (strpos($segment, '/') === 0);
18885:                 if ($is_end_tag) {
18886:                     $type = substr($segment, 1);
18887:                     $token = new HTMLPurifier_Token_End($type);
18888:                     if ($maintain_line_numbers) {
18889:                         $token->rawPosition($current_line, $current_col);
18890:                         $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
18891:                     }
18892:                     $array[] = $token;
18893:                     $inside_tag = false;
18894:                     $cursor = $position_next_gt + 1;
18895:                     continue;
18896:                 }
18897: 
18898:                 
18899:                 
18900:                 
18901:                 if (!ctype_alpha($segment[0])) {
18902:                     
18903:                     if ($e) {
18904:                         $e->send(E_NOTICE, 'Lexer: Unescaped lt');
18905:                     }
18906:                     $token = new HTMLPurifier_Token_Text('<');
18907:                     if ($maintain_line_numbers) {
18908:                         $token->rawPosition($current_line, $current_col);
18909:                         $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
18910:                     }
18911:                     $array[] = $token;
18912:                     $inside_tag = false;
18913:                     continue;
18914:                 }
18915: 
18916:                 
18917:                 
18918:                 
18919:                 
18920:                 $is_self_closing = (strrpos($segment, '/') === $strlen_segment - 1);
18921:                 if ($is_self_closing) {
18922:                     $strlen_segment--;
18923:                     $segment = substr($segment, 0, $strlen_segment);
18924:                 }
18925: 
18926:                 
18927:                 $position_first_space = strcspn($segment, $this->_whitespace);
18928: 
18929:                 if ($position_first_space >= $strlen_segment) {
18930:                     if ($is_self_closing) {
18931:                         $token = new HTMLPurifier_Token_Empty($segment);
18932:                     } else {
18933:                         $token = new HTMLPurifier_Token_Start($segment);
18934:                     }
18935:                     if ($maintain_line_numbers) {
18936:                         $token->rawPosition($current_line, $current_col);
18937:                         $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
18938:                     }
18939:                     $array[] = $token;
18940:                     $inside_tag = false;
18941:                     $cursor = $position_next_gt + 1;
18942:                     continue;
18943:                 }
18944: 
18945:                 
18946:                 $type = substr($segment, 0, $position_first_space);
18947:                 $attribute_string =
18948:                     trim(
18949:                         substr(
18950:                             $segment,
18951:                             $position_first_space
18952:                         )
18953:                     );
18954:                 if ($attribute_string) {
18955:                     $attr = $this->parseAttributeString(
18956:                         $attribute_string,
18957:                         $config,
18958:                         $context
18959:                     );
18960:                 } else {
18961:                     $attr = array();
18962:                 }
18963: 
18964:                 if ($is_self_closing) {
18965:                     $token = new HTMLPurifier_Token_Empty($type, $attr);
18966:                 } else {
18967:                     $token = new HTMLPurifier_Token_Start($type, $attr);
18968:                 }
18969:                 if ($maintain_line_numbers) {
18970:                     $token->rawPosition($current_line, $current_col);
18971:                     $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
18972:                 }
18973:                 $array[] = $token;
18974:                 $cursor = $position_next_gt + 1;
18975:                 $inside_tag = false;
18976:                 continue;
18977:             } else {
18978:                 
18979:                 if ($e) {
18980:                     $e->send(E_WARNING, 'Lexer: Missing gt');
18981:                 }
18982:                 $token = new
18983:                 HTMLPurifier_Token_Text(
18984:                     '<' .
18985:                     $this->parseData(
18986:                         substr($html, $cursor)
18987:                     )
18988:                 );
18989:                 if ($maintain_line_numbers) {
18990:                     $token->rawPosition($current_line, $current_col);
18991:                 }
18992:                 
18993:                 $array[] = $token;
18994:                 break;
18995:             }
18996:             break;
18997:         }
18998: 
18999:         $context->destroy('CurrentLine');
19000:         $context->destroy('CurrentCol');
19001:         return $array;
19002:     }
19003: 
19004:     19005: 19006: 19007: 19008: 19009: 19010: 19011: 
19012:     protected function substrCount($haystack, $needle, $offset, $length)
19013:     {
19014:         static $oldVersion;
19015:         if ($oldVersion === null) {
19016:             $oldVersion = version_compare(PHP_VERSION, '5.1', '<');
19017:         }
19018:         if ($oldVersion) {
19019:             $haystack = substr($haystack, $offset, $length);
19020:             return substr_count($haystack, $needle);
19021:         } else {
19022:             return substr_count($haystack, $needle, $offset, $length);
19023:         }
19024:     }
19025: 
19026:     19027: 19028: 19029: 19030: 19031: 19032: 19033: 
19034:     public function parseAttributeString($string, $config, $context)
19035:     {
19036:         $string = (string)$string; 
19037: 
19038:         if ($string == '') {
19039:             return array();
19040:         } 
19041: 
19042:         $e = false;
19043:         if ($config->get('Core.CollectErrors')) {
19044:             $e =& $context->get('ErrorCollector');
19045:         }
19046: 
19047:         
19048:         
19049:         $num_equal = substr_count($string, '=');
19050:         $has_space = strpos($string, ' ');
19051:         if ($num_equal === 0 && !$has_space) {
19052:             
19053:             return array($string => $string);
19054:         } elseif ($num_equal === 1 && !$has_space) {
19055:             
19056:             list($key, $quoted_value) = explode('=', $string);
19057:             $quoted_value = trim($quoted_value);
19058:             if (!$key) {
19059:                 if ($e) {
19060:                     $e->send(E_ERROR, 'Lexer: Missing attribute key');
19061:                 }
19062:                 return array();
19063:             }
19064:             if (!$quoted_value) {
19065:                 return array($key => '');
19066:             }
19067:             $first_char = @$quoted_value[0];
19068:             $last_char = @$quoted_value[strlen($quoted_value) - 1];
19069: 
19070:             $same_quote = ($first_char == $last_char);
19071:             $open_quote = ($first_char == '"' || $first_char == "'");
19072: 
19073:             if ($same_quote && $open_quote) {
19074:                 
19075:                 $value = substr($quoted_value, 1, strlen($quoted_value) - 2);
19076:             } else {
19077:                 
19078:                 if ($open_quote) {
19079:                     if ($e) {
19080:                         $e->send(E_ERROR, 'Lexer: Missing end quote');
19081:                     }
19082:                     $value = substr($quoted_value, 1);
19083:                 } else {
19084:                     $value = $quoted_value;
19085:                 }
19086:             }
19087:             if ($value === false) {
19088:                 $value = '';
19089:             }
19090:             return array($key => $this->parseData($value));
19091:         }
19092: 
19093:         
19094:         $array = array(); 
19095:         $cursor = 0; 
19096:         $size = strlen($string); 
19097: 
19098:         
19099:         
19100:         $string .= ' ';
19101: 
19102:         $old_cursor = -1;
19103:         while ($cursor < $size) {
19104:             if ($old_cursor >= $cursor) {
19105:                 throw new Exception("Infinite loop detected");
19106:             }
19107:             $old_cursor = $cursor;
19108: 
19109:             $cursor += ($value = strspn($string, $this->_whitespace, $cursor));
19110:             
19111: 
19112:             $key_begin = $cursor; 
19113: 
19114:             
19115:             $cursor += strcspn($string, $this->_whitespace . '=', $cursor);
19116: 
19117:             $key_end = $cursor; 
19118: 
19119:             $key = substr($string, $key_begin, $key_end - $key_begin);
19120: 
19121:             if (!$key) {
19122:                 if ($e) {
19123:                     $e->send(E_ERROR, 'Lexer: Missing attribute key');
19124:                 }
19125:                 $cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1); 
19126:                 continue; 
19127:             }
19128: 
19129:             
19130:             $cursor += strspn($string, $this->_whitespace, $cursor);
19131: 
19132:             if ($cursor >= $size) {
19133:                 $array[$key] = $key;
19134:                 break;
19135:             }
19136: 
19137:             
19138:             
19139:             $first_char = @$string[$cursor];
19140: 
19141:             if ($first_char == '=') {
19142:                 
19143: 
19144:                 $cursor++;
19145:                 $cursor += strspn($string, $this->_whitespace, $cursor);
19146: 
19147:                 if ($cursor === false) {
19148:                     $array[$key] = '';
19149:                     break;
19150:                 }
19151: 
19152:                 
19153: 
19154:                 $char = @$string[$cursor];
19155: 
19156:                 if ($char == '"' || $char == "'") {
19157:                     
19158:                     $cursor++;
19159:                     $value_begin = $cursor;
19160:                     $cursor = strpos($string, $char, $cursor);
19161:                     $value_end = $cursor;
19162:                 } else {
19163:                     
19164:                     $value_begin = $cursor;
19165:                     $cursor += strcspn($string, $this->_whitespace, $cursor);
19166:                     $value_end = $cursor;
19167:                 }
19168: 
19169:                 
19170:                 if ($cursor === false) {
19171:                     $cursor = $size;
19172:                     $value_end = $cursor;
19173:                 }
19174: 
19175:                 $value = substr($string, $value_begin, $value_end - $value_begin);
19176:                 if ($value === false) {
19177:                     $value = '';
19178:                 }
19179:                 $array[$key] = $this->parseData($value);
19180:                 $cursor++;
19181:             } else {
19182:                 
19183:                 if ($key !== '') {
19184:                     $array[$key] = $key;
19185:                 } else {
19186:                     
19187:                     if ($e) {
19188:                         $e->send(E_ERROR, 'Lexer: Missing attribute key');
19189:                     }
19190:                 }
19191:             }
19192:         }
19193:         return $array;
19194:     }
19195: }
19196: 
19197: 
19198: 
19199: 
19200: 
19201: 19202: 19203: 
19204: class  extends HTMLPurifier_Node
19205: {
19206:     19207: 19208: 19209: 
19210:     public $data;
19211: 
19212:     19213: 19214: 
19215:     public $is_whitespace = true;
19216: 
19217:     19218: 19219: 19220: 19221: 19222: 19223: 
19224:     public function __construct($data, $line = null, $col = null)
19225:     {
19226:         $this->data = $data;
19227:         $this->line = $line;
19228:         $this->col = $col;
19229:     }
19230: 
19231:     public function toTokenPair() {
19232:         return array(new HTMLPurifier_Token_Comment($this->data, $this->line, $this->col), null);
19233:     }
19234: }
19235: 
19236: 
19237: 
19238: 19239: 19240: 
19241: class HTMLPurifier_Node_Element extends HTMLPurifier_Node
19242: {
19243:     19244: 19245: 19246: 19247: 19248: 19249: 19250: 
19251:     public $name;
19252: 
19253:     19254: 19255: 19256: 
19257:     public $attr = array();
19258: 
19259:     19260: 19261: 19262: 
19263:     public $children = array();
19264: 
19265:     19266: 19267: 19268: 19269: 
19270:     public $empty = false;
19271: 
19272:     public $endCol = null, $endLine = null, $endArmor = array();
19273: 
19274:     public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) {
19275:         $this->name = $name;
19276:         $this->attr = $attr;
19277:         $this->line = $line;
19278:         $this->col = $col;
19279:         $this->armor = $armor;
19280:     }
19281: 
19282:     public function toTokenPair() {
19283:         
19284:         if ($this->empty) {
19285:             return array(new HTMLPurifier_Token_Empty($this->name, $this->attr, $this->line, $this->col, $this->armor), null);
19286:         } else {
19287:             $start = new HTMLPurifier_Token_Start($this->name, $this->attr, $this->line, $this->col, $this->armor);
19288:             $end = new HTMLPurifier_Token_End($this->name, array(), $this->endLine, $this->endCol, $this->endArmor);
19289:             
19290:             return array($start, $end);
19291:         }
19292:     }
19293: }
19294: 
19295: 
19296: 
19297: 
19298: 19299: 19300: 19301: 19302: 19303: 19304: 19305: 19306: 
19307: class HTMLPurifier_Node_Text extends HTMLPurifier_Node
19308: {
19309: 
19310:     19311: 19312: 19313: 19314: 
19315:     public $name = '#PCDATA';
19316: 
19317:     19318: 19319: 
19320:     public $data;
19321:     
19322: 
19323:     19324: 19325: 
19326:     public $is_whitespace;
19327: 
19328:     
19329: 
19330:     19331: 19332: 19333: 19334: 19335: 
19336:     public function __construct($data, $is_whitespace, $line = null, $col = null)
19337:     {
19338:         $this->data = $data;
19339:         $this->is_whitespace = $is_whitespace;
19340:         $this->line = $line;
19341:         $this->col = $col;
19342:     }
19343: 
19344:     public function toTokenPair() {
19345:         return array(new HTMLPurifier_Token_Text($this->data, $this->line, $this->col), null);
19346:     }
19347: }
19348: 
19349: 
19350: 
19351: 
19352: 
19353: 19354: 19355: 
19356: abstract class HTMLPurifier_Strategy_Composite extends HTMLPurifier_Strategy
19357: {
19358: 
19359:     19360: 19361: 19362: 
19363:     protected $strategies = array();
19364: 
19365:     19366: 19367: 19368: 19369: 19370: 
19371:     public function execute($tokens, $config, $context)
19372:     {
19373:         foreach ($this->strategies as $strategy) {
19374:             $tokens = $strategy->execute($tokens, $config, $context);
19375:         }
19376:         return $tokens;
19377:     }
19378: }
19379: 
19380: 
19381: 
19382: 
19383: 
19384: 19385: 19386: 
19387: class HTMLPurifier_Strategy_Core extends HTMLPurifier_Strategy_Composite
19388: {
19389:     public function __construct()
19390:     {
19391:         $this->strategies[] = new HTMLPurifier_Strategy_RemoveForeignElements();
19392:         $this->strategies[] = new HTMLPurifier_Strategy_MakeWellFormed();
19393:         $this->strategies[] = new HTMLPurifier_Strategy_FixNesting();
19394:         $this->strategies[] = new HTMLPurifier_Strategy_ValidateAttributes();
19395:     }
19396: }
19397: 
19398: 
19399: 
19400: 
19401: 
19402: 19403: 19404: 19405: 19406: 19407: 19408: 19409: 19410: 19411: 19412: 19413: 19414: 19415: 19416: 19417: 19418: 19419: 19420: 19421: 19422: 19423: 19424: 19425: 19426: 19427: 19428: 19429: 
19430: 
19431: class HTMLPurifier_Strategy_FixNesting extends HTMLPurifier_Strategy
19432: {
19433: 
19434:     19435: 19436: 19437: 19438: 19439: 
19440:     public function execute($tokens, $config, $context)
19441:     {
19442: 
19443:         
19444:         
19445: 
19446:         
19447:         
19448:         $top_node = HTMLPurifier_Arborize::arborize($tokens, $config, $context);
19449: 
19450:         
19451:         $definition = $config->getHTMLDefinition();
19452: 
19453:         $excludes_enabled = !$config->get('Core.DisableExcludes');
19454: 
19455:         
19456:         
19457:         
19458:         
19459:         $is_inline = $definition->info_parent_def->descendants_are_inline;
19460:         $context->register('IsInline', $is_inline);
19461: 
19462:         
19463:         $e =& $context->get('ErrorCollector', true);
19464: 
19465:         
19466:         
19467: 
19468:         
19469:         
19470:         
19471:         
19472:         $exclude_stack = array($definition->info_parent_def->excludes);
19473: 
19474:         
19475:         
19476:         $node = $top_node;
19477:         
19478:         list($token, $d) = $node->toTokenPair();
19479:         $context->register('CurrentNode', $node);
19480:         $context->register('CurrentToken', $token);
19481: 
19482:         
19483:         
19484: 
19485:         
19486:         
19487:         
19488:         
19489:         
19490:         
19491:         
19492:         
19493:         
19494:         
19495:         
19496:         
19497:         
19498:         
19499:         
19500:         
19501: 
19502:         $parent_def = $definition->info_parent_def;
19503:         $stack = array(
19504:             array($top_node,
19505:                   $parent_def->descendants_are_inline,
19506:                   $parent_def->excludes, 
19507:                   0)
19508:             );
19509: 
19510:         while (!empty($stack)) {
19511:             list($node, $is_inline, $excludes, $ix) = array_pop($stack);
19512:             
19513:             $go = false;
19514:             $def = empty($stack) ? $definition->info_parent_def : $definition->info[$node->name];
19515:             while (isset($node->children[$ix])) {
19516:                 $child = $node->children[$ix++];
19517:                 if ($child instanceof HTMLPurifier_Node_Element) {
19518:                     $go = true;
19519:                     $stack[] = array($node, $is_inline, $excludes, $ix);
19520:                     $stack[] = array($child,
19521:                         
19522:                         
19523:                         $is_inline || $def->descendants_are_inline,
19524:                         empty($def->excludes) ? $excludes
19525:                                               : array_merge($excludes, $def->excludes),
19526:                         0);
19527:                     break;
19528:                 }
19529:             };
19530:             if ($go) continue;
19531:             list($token, $d) = $node->toTokenPair();
19532:             
19533:             if ($excludes_enabled && isset($excludes[$node->name])) {
19534:                 $node->dead = true;
19535:                 if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded');
19536:             } else {
19537:                 
19538:                 
19539:                 
19540:                 $children = array();
19541:                 foreach ($node->children as $child) {
19542:                     if (!$child->dead) $children[] = $child;
19543:                 }
19544:                 $result = $def->child->validateChildren($children, $config, $context);
19545:                 if ($result === true) {
19546:                     
19547:                     $node->children = $children;
19548:                 } elseif ($result === false) {
19549:                     $node->dead = true;
19550:                     if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node removed');
19551:                 } else {
19552:                     $node->children = $result;
19553:                     if ($e) {
19554:                         
19555:                         if (empty($result) && !empty($children)) {
19556:                             $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed');
19557:                         } else if ($result != $children) {
19558:                             $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized');
19559:                         }
19560:                     }
19561:                 }
19562:             }
19563:         }
19564: 
19565:         
19566:         
19567: 
19568:         
19569:         $context->destroy('IsInline');
19570:         $context->destroy('CurrentNode');
19571:         $context->destroy('CurrentToken');
19572: 
19573:         
19574:         
19575: 
19576:         return HTMLPurifier_Arborize::flatten($node, $config, $context);
19577:     }
19578: }
19579: 
19580: 
19581: 
19582: 
19583: 
19584: 19585: 19586: 19587: 19588: 19589: 19590: 19591: 19592: 19593: 19594: 
19595: class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
19596: {
19597: 
19598:     19599: 19600: 19601: 
19602:     protected $tokens;
19603: 
19604:     19605: 19606: 19607: 
19608:     protected $token;
19609: 
19610:     19611: 19612: 19613: 
19614:     protected $zipper;
19615: 
19616:     19617: 19618: 19619: 
19620:     protected $stack;
19621: 
19622:     19623: 19624: 19625: 
19626:     protected $injectors;
19627: 
19628:     19629: 19630: 19631: 
19632:     protected $config;
19633: 
19634:     19635: 19636: 19637: 
19638:     protected $context;
19639: 
19640:     19641: 19642: 19643: 19644: 19645: 19646: 
19647:     public function execute($tokens, $config, $context)
19648:     {
19649:         $definition = $config->getHTMLDefinition();
19650: 
19651:         
19652:         $generator = new HTMLPurifier_Generator($config, $context);
19653:         $escape_invalid_tags = $config->get('Core.EscapeInvalidTags');
19654:         
19655:         $global_parent_allowed_elements = $definition->info_parent_def->child->getAllowedElements($config);
19656:         $e = $context->get('ErrorCollector', true);
19657:         $i = false; 
19658:         list($zipper, $token) = HTMLPurifier_Zipper::fromArray($tokens);
19659:         if ($token === NULL) {
19660:             return array();
19661:         }
19662:         $reprocess = false; 
19663:         $stack = array();
19664: 
19665:         
19666:         $this->stack =& $stack;
19667:         $this->tokens =& $tokens;
19668:         $this->token =& $token;
19669:         $this->zipper =& $zipper;
19670:         $this->config = $config;
19671:         $this->context = $context;
19672: 
19673:         
19674:         $context->register('CurrentNesting', $stack);
19675:         $context->register('InputZipper', $zipper);
19676:         $context->register('CurrentToken', $token);
19677: 
19678:         
19679: 
19680:         $this->injectors = array();
19681: 
19682:         $injectors = $config->getBatch('AutoFormat');
19683:         $def_injectors = $definition->info_injector;
19684:         $custom_injectors = $injectors['Custom'];
19685:         unset($injectors['Custom']); 
19686:         foreach ($injectors as $injector => $b) {
19687:             
19688:             if (strpos($injector, '.') !== false) {
19689:                 continue;
19690:             }
19691:             $injector = "HTMLPurifier_Injector_$injector";
19692:             if (!$b) {
19693:                 continue;
19694:             }
19695:             $this->injectors[] = new $injector;
19696:         }
19697:         foreach ($def_injectors as $injector) {
19698:             
19699:             $this->injectors[] = $injector;
19700:         }
19701:         foreach ($custom_injectors as $injector) {
19702:             if (!$injector) {
19703:                 continue;
19704:             }
19705:             if (is_string($injector)) {
19706:                 $injector = "HTMLPurifier_Injector_$injector";
19707:                 $injector = new $injector;
19708:             }
19709:             $this->injectors[] = $injector;
19710:         }
19711: 
19712:         
19713:         
19714:         foreach ($this->injectors as $ix => $injector) {
19715:             $error = $injector->prepare($config, $context);
19716:             if (!$error) {
19717:                 continue;
19718:             }
19719:             array_splice($this->injectors, $ix, 1); 
19720:             trigger_error("Cannot enable {$injector->name} injector because $error is not allowed", E_USER_WARNING);
19721:         }
19722: 
19723:         
19724: 
19725:         
19726:         
19727:         
19728:         
19729:         
19730:         
19731:         
19732: 
19733:         
19734:         for (;;
19735:              
19736:              $reprocess ? $reprocess = false : $token = $zipper->next($token)) {
19737: 
19738:             
19739:             if (is_int($i)) {
19740:                 
19741:                 
19742:                 
19743:                 $rewind_offset = $this->injectors[$i]->getRewindOffset();
19744:                 if (is_int($rewind_offset)) {
19745:                     for ($j = 0; $j < $rewind_offset; $j++) {
19746:                         if (empty($zipper->front)) break;
19747:                         $token = $zipper->prev($token);
19748:                         
19749:                         
19750:                         unset($token->skip[$i]);
19751:                         $token->rewind = $i;
19752:                         if ($token instanceof HTMLPurifier_Token_Start) {
19753:                             array_pop($this->stack);
19754:                         } elseif ($token instanceof HTMLPurifier_Token_End) {
19755:                             $this->stack[] = $token->start;
19756:                         }
19757:                     }
19758:                 }
19759:                 $i = false;
19760:             }
19761: 
19762:             
19763:             if ($token === NULL) {
19764:                 
19765:                 if (empty($this->stack)) {
19766:                     break;
19767:                 }
19768: 
19769:                 
19770:                 $top_nesting = array_pop($this->stack);
19771:                 $this->stack[] = $top_nesting;
19772: 
19773:                 
19774:                 if ($e && !isset($top_nesting->armor['MakeWellFormed_TagClosedError'])) {
19775:                     $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', $top_nesting);
19776:                 }
19777: 
19778:                 
19779:                 $token = new HTMLPurifier_Token_End($top_nesting->name);
19780: 
19781:                 
19782:                 $reprocess = true;
19783:                 continue;
19784:             }
19785: 
19786:             
19787:             
19788: 
19789:             
19790:             if (empty($token->is_tag)) {
19791:                 if ($token instanceof HTMLPurifier_Token_Text) {
19792:                     foreach ($this->injectors as $i => $injector) {
19793:                         if (isset($token->skip[$i])) {
19794:                             continue;
19795:                         }
19796:                         if ($token->rewind !== null && $token->rewind !== $i) {
19797:                             continue;
19798:                         }
19799:                         
19800:                         $r = $token;
19801:                         $injector->handleText($r);
19802:                         $token = $this->processToken($r, $i);
19803:                         $reprocess = true;
19804:                         break;
19805:                     }
19806:                 }
19807:                 
19808:                 continue;
19809:             }
19810: 
19811:             if (isset($definition->info[$token->name])) {
19812:                 $type = $definition->info[$token->name]->child->type;
19813:             } else {
19814:                 $type = false; 
19815:             }
19816: 
19817:             
19818:             $ok = false;
19819:             if ($type === 'empty' && $token instanceof HTMLPurifier_Token_Start) {
19820:                 
19821:                 $token = new HTMLPurifier_Token_Empty(
19822:                     $token->name,
19823:                     $token->attr,
19824:                     $token->line,
19825:                     $token->col,
19826:                     $token->armor
19827:                 );
19828:                 $ok = true;
19829:             } elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) {
19830:                 
19831:                 
19832:                 $old_token = $token;
19833:                 $token = new HTMLPurifier_Token_End($token->name);
19834:                 $token = $this->insertBefore(
19835:                     new HTMLPurifier_Token_Start($old_token->name, $old_token->attr, $old_token->line, $old_token->col, $old_token->armor)
19836:                 );
19837:                 
19838:                 $reprocess = true;
19839:                 continue;
19840:             } elseif ($token instanceof HTMLPurifier_Token_Empty) {
19841:                 
19842:                 $ok = true;
19843:             } elseif ($token instanceof HTMLPurifier_Token_Start) {
19844:                 
19845: 
19846:                 
19847:                 if (!empty($this->stack)) {
19848: 
19849:                     
19850:                     
19851:                     
19852:                     
19853:                     
19854:                     
19855:                     
19856:                     
19857:                     
19858:                     
19859:                     
19860:                     
19861: 
19862:                     $parent = array_pop($this->stack);
19863:                     $this->stack[] = $parent;
19864: 
19865:                     $parent_def = null;
19866:                     $parent_elements = null;
19867:                     $autoclose = false;
19868:                     if (isset($definition->info[$parent->name])) {
19869:                         $parent_def = $definition->info[$parent->name];
19870:                         $parent_elements = $parent_def->child->getAllowedElements($config);
19871:                         $autoclose = !isset($parent_elements[$token->name]);
19872:                     }
19873: 
19874:                     if ($autoclose && $definition->info[$token->name]->wrap) {
19875:                         
19876:                         
19877:                         
19878:                         $wrapname = $definition->info[$token->name]->wrap;
19879:                         $wrapdef = $definition->info[$wrapname];
19880:                         $elements = $wrapdef->child->getAllowedElements($config);
19881:                         if (isset($elements[$token->name]) && isset($parent_elements[$wrapname])) {
19882:                             $newtoken = new HTMLPurifier_Token_Start($wrapname);
19883:                             $token = $this->insertBefore($newtoken);
19884:                             $reprocess = true;
19885:                             continue;
19886:                         }
19887:                     }
19888: 
19889:                     $carryover = false;
19890:                     if ($autoclose && $parent_def->formatting) {
19891:                         $carryover = true;
19892:                     }
19893: 
19894:                     if ($autoclose) {
19895:                         
19896:                         
19897:                         $autoclose_ok = isset($global_parent_allowed_elements[$token->name]);
19898:                         if (!$autoclose_ok) {
19899:                             foreach ($this->stack as $ancestor) {
19900:                                 $elements = $definition->info[$ancestor->name]->child->getAllowedElements($config);
19901:                                 if (isset($elements[$token->name])) {
19902:                                     $autoclose_ok = true;
19903:                                     break;
19904:                                 }
19905:                                 if ($definition->info[$token->name]->wrap) {
19906:                                     $wrapname = $definition->info[$token->name]->wrap;
19907:                                     $wrapdef = $definition->info[$wrapname];
19908:                                     $wrap_elements = $wrapdef->child->getAllowedElements($config);
19909:                                     if (isset($wrap_elements[$token->name]) && isset($elements[$wrapname])) {
19910:                                         $autoclose_ok = true;
19911:                                         break;
19912:                                     }
19913:                                 }
19914:                             }
19915:                         }
19916:                         if ($autoclose_ok) {
19917:                             
19918:                             $new_token = new HTMLPurifier_Token_End($parent->name);
19919:                             $new_token->start = $parent;
19920:                             
19921:                             if ($e && !isset($parent->armor['MakeWellFormed_TagClosedError'])) {
19922:                                 if (!$carryover) {
19923:                                     $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', $parent);
19924:                                 } else {
19925:                                     $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag carryover', $parent);
19926:                                 }
19927:                             }
19928:                             if ($carryover) {
19929:                                 $element = clone $parent;
19930:                                 
19931:                                 $element->armor['MakeWellFormed_TagClosedError'] = true;
19932:                                 $element->carryover = true;
19933:                                 $token = $this->processToken(array($new_token, $token, $element));
19934:                             } else {
19935:                                 $token = $this->insertBefore($new_token);
19936:                             }
19937:                         } else {
19938:                             $token = $this->remove();
19939:                         }
19940:                         $reprocess = true;
19941:                         continue;
19942:                     }
19943: 
19944:                 }
19945:                 $ok = true;
19946:             }
19947: 
19948:             if ($ok) {
19949:                 foreach ($this->injectors as $i => $injector) {
19950:                     if (isset($token->skip[$i])) {
19951:                         continue;
19952:                     }
19953:                     if ($token->rewind !== null && $token->rewind !== $i) {
19954:                         continue;
19955:                     }
19956:                     $r = $token;
19957:                     $injector->handleElement($r);
19958:                     $token = $this->processToken($r, $i);
19959:                     $reprocess = true;
19960:                     break;
19961:                 }
19962:                 if (!$reprocess) {
19963:                     
19964:                     if ($token instanceof HTMLPurifier_Token_Start) {
19965:                         $this->stack[] = $token;
19966:                     } elseif ($token instanceof HTMLPurifier_Token_End) {
19967:                         throw new HTMLPurifier_Exception(
19968:                             'Improper handling of end tag in start code; possible error in MakeWellFormed'
19969:                         );
19970:                     }
19971:                 }
19972:                 continue;
19973:             }
19974: 
19975:             
19976:             if (!$token instanceof HTMLPurifier_Token_End) {
19977:                 throw new HTMLPurifier_Exception('Unaccounted for tag token in input stream, bug in HTML Purifier');
19978:             }
19979: 
19980:             
19981:             if (empty($this->stack)) {
19982:                 if ($escape_invalid_tags) {
19983:                     if ($e) {
19984:                         $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text');
19985:                     }
19986:                     $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));
19987:                 } else {
19988:                     if ($e) {
19989:                         $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed');
19990:                     }
19991:                     $token = $this->remove();
19992:                 }
19993:                 $reprocess = true;
19994:                 continue;
19995:             }
19996: 
19997:             
19998:             
19999:             
20000:             
20001:             $current_parent = array_pop($this->stack);
20002:             if ($current_parent->name == $token->name) {
20003:                 $token->start = $current_parent;
20004:                 foreach ($this->injectors as $i => $injector) {
20005:                     if (isset($token->skip[$i])) {
20006:                         continue;
20007:                     }
20008:                     if ($token->rewind !== null && $token->rewind !== $i) {
20009:                         continue;
20010:                     }
20011:                     $r = $token;
20012:                     $injector->handleEnd($r);
20013:                     $token = $this->processToken($r, $i);
20014:                     $this->stack[] = $current_parent;
20015:                     $reprocess = true;
20016:                     break;
20017:                 }
20018:                 continue;
20019:             }
20020: 
20021:             
20022: 
20023:             
20024:             $this->stack[] = $current_parent;
20025: 
20026:             
20027:             
20028:             $size = count($this->stack);
20029:             
20030:             $skipped_tags = false;
20031:             for ($j = $size - 2; $j >= 0; $j--) {
20032:                 if ($this->stack[$j]->name == $token->name) {
20033:                     $skipped_tags = array_slice($this->stack, $j);
20034:                     break;
20035:                 }
20036:             }
20037: 
20038:             
20039:             if ($skipped_tags === false) {
20040:                 if ($escape_invalid_tags) {
20041:                     if ($e) {
20042:                         $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text');
20043:                     }
20044:                     $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));
20045:                 } else {
20046:                     if ($e) {
20047:                         $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed');
20048:                     }
20049:                     $token = $this->remove();
20050:                 }
20051:                 $reprocess = true;
20052:                 continue;
20053:             }
20054: 
20055:             
20056:             $c = count($skipped_tags);
20057:             if ($e) {
20058:                 for ($j = $c - 1; $j > 0; $j--) {
20059:                     
20060:                     
20061:                     if (!isset($skipped_tags[$j]->armor['MakeWellFormed_TagClosedError'])) {
20062:                         $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', $skipped_tags[$j]);
20063:                     }
20064:                 }
20065:             }
20066: 
20067:             
20068:             $replace = array($token);
20069:             for ($j = 1; $j < $c; $j++) {
20070:                 
20071:                 $new_token = new HTMLPurifier_Token_End($skipped_tags[$j]->name);
20072:                 $new_token->start = $skipped_tags[$j];
20073:                 array_unshift($replace, $new_token);
20074:                 if (isset($definition->info[$new_token->name]) && $definition->info[$new_token->name]->formatting) {
20075:                     
20076:                     $element = clone $skipped_tags[$j];
20077:                     $element->carryover = true;
20078:                     $element->armor['MakeWellFormed_TagClosedError'] = true;
20079:                     $replace[] = $element;
20080:                 }
20081:             }
20082:             $token = $this->processToken($replace);
20083:             $reprocess = true;
20084:             continue;
20085:         }
20086: 
20087:         $context->destroy('CurrentToken');
20088:         $context->destroy('CurrentNesting');
20089:         $context->destroy('InputZipper');
20090: 
20091:         unset($this->injectors, $this->stack, $this->tokens);
20092:         return $zipper->toArray($token);
20093:     }
20094: 
20095:     20096: 20097: 20098: 20099: 20100: 20101: 20102: 20103: 20104: 20105: 20106: 20107: 20108: 20109: 20110: 20111: 20112: 20113: 20114: 20115: 
20116:     protected function processToken($token, $injector = -1)
20117:     {
20118:         
20119:         if (is_object($token)) {
20120:             $token = array(1, $token);
20121:         }
20122:         if (is_int($token)) {
20123:             $token = array($token);
20124:         }
20125:         if ($token === false) {
20126:             $token = array(1);
20127:         }
20128:         if (!is_array($token)) {
20129:             throw new HTMLPurifier_Exception('Invalid token type from injector');
20130:         }
20131:         if (!is_int($token[0])) {
20132:             array_unshift($token, 1);
20133:         }
20134:         if ($token[0] === 0) {
20135:             throw new HTMLPurifier_Exception('Deleting zero tokens is not valid');
20136:         }
20137: 
20138:         
20139:         
20140: 
20141:         $delete = array_shift($token);
20142:         list($old, $r) = $this->zipper->splice($this->token, $delete, $token);
20143: 
20144:         if ($injector > -1) {
20145:             
20146:             $oldskip = isset($old[0]) ? $old[0]->skip : array();
20147:             foreach ($token as $object) {
20148:                 $object->skip = $oldskip;
20149:                 $object->skip[$injector] = true;
20150:             }
20151:         }
20152: 
20153:         return $r;
20154: 
20155:     }
20156: 
20157:     20158: 20159: 20160: 20161: 
20162:     private function insertBefore($token)
20163:     {
20164:         
20165:         
20166:         $splice = $this->zipper->splice($this->token, 0, array($token));
20167: 
20168:         return $splice[1];
20169:     }
20170: 
20171:     20172: 20173: 20174: 
20175:     private function remove()
20176:     {
20177:         return $this->zipper->delete();
20178:     }
20179: }
20180: 
20181: 
20182: 
20183: 
20184: 
20185: 20186: 20187: 20188: 20189: 20190: 20191: 
20192: 
20193: class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy
20194: {
20195: 
20196:     20197: 20198: 20199: 20200: 20201: 
20202:     public function execute($tokens, $config, $context)
20203:     {
20204:         $definition = $config->getHTMLDefinition();
20205:         $generator = new HTMLPurifier_Generator($config, $context);
20206:         $result = array();
20207: 
20208:         $escape_invalid_tags = $config->get('Core.EscapeInvalidTags');
20209:         $remove_invalid_img = $config->get('Core.RemoveInvalidImg');
20210: 
20211:         
20212:         $trusted = $config->get('HTML.Trusted');
20213:         $comment_lookup = $config->get('HTML.AllowedComments');
20214:         $comment_regexp = $config->get('HTML.AllowedCommentsRegexp');
20215:         $check_comments = $comment_lookup !== array() || $comment_regexp !== null;
20216: 
20217:         $remove_script_contents = $config->get('Core.RemoveScriptContents');
20218:         $hidden_elements = $config->get('Core.HiddenElements');
20219: 
20220:         
20221:         if ($remove_script_contents === true) {
20222:             $hidden_elements['script'] = true;
20223:         } elseif ($remove_script_contents === false && isset($hidden_elements['script'])) {
20224:             unset($hidden_elements['script']);
20225:         }
20226: 
20227:         $attr_validator = new HTMLPurifier_AttrValidator();
20228: 
20229:         
20230:         $remove_until = false;
20231: 
20232:         
20233:         $textify_comments = false;
20234: 
20235:         $token = false;
20236:         $context->register('CurrentToken', $token);
20237: 
20238:         $e = false;
20239:         if ($config->get('Core.CollectErrors')) {
20240:             $e =& $context->get('ErrorCollector');
20241:         }
20242: 
20243:         foreach ($tokens as $token) {
20244:             if ($remove_until) {
20245:                 if (empty($token->is_tag) || $token->name !== $remove_until) {
20246:                     continue;
20247:                 }
20248:             }
20249:             if (!empty($token->is_tag)) {
20250:                 
20251: 
20252:                 
20253:                 if (isset($definition->info_tag_transform[$token->name])) {
20254:                     $original_name = $token->name;
20255:                     
20256:                     
20257:                     $token = $definition->
20258:                         info_tag_transform[$token->name]->transform($token, $config, $context);
20259:                     if ($e) {
20260:                         $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', $original_name);
20261:                     }
20262:                 }
20263: 
20264:                 if (isset($definition->info[$token->name])) {
20265:                     
20266:                     
20267:                     if (($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) &&
20268:                         $definition->info[$token->name]->required_attr &&
20269:                         ($token->name != 'img' || $remove_invalid_img) 
20270:                     ) {
20271:                         $attr_validator->validateToken($token, $config, $context);
20272:                         $ok = true;
20273:                         foreach ($definition->info[$token->name]->required_attr as $name) {
20274:                             if (!isset($token->attr[$name])) {
20275:                                 $ok = false;
20276:                                 break;
20277:                             }
20278:                         }
20279:                         if (!$ok) {
20280:                             if ($e) {
20281:                                 $e->send(
20282:                                     E_ERROR,
20283:                                     'Strategy_RemoveForeignElements: Missing required attribute',
20284:                                     $name
20285:                                 );
20286:                             }
20287:                             continue;
20288:                         }
20289:                         $token->armor['ValidateAttributes'] = true;
20290:                     }
20291: 
20292:                     if (isset($hidden_elements[$token->name]) && $token instanceof HTMLPurifier_Token_Start) {
20293:                         $textify_comments = $token->name;
20294:                     } elseif ($token->name === $textify_comments && $token instanceof HTMLPurifier_Token_End) {
20295:                         $textify_comments = false;
20296:                     }
20297: 
20298:                 } elseif ($escape_invalid_tags) {
20299:                     
20300:                     if ($e) {
20301:                         $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text');
20302:                     }
20303:                     $token = new HTMLPurifier_Token_Text(
20304:                         $generator->generateFromToken($token)
20305:                     );
20306:                 } else {
20307:                     
20308:                     
20309:                     if (isset($hidden_elements[$token->name])) {
20310:                         if ($token instanceof HTMLPurifier_Token_Start) {
20311:                             $remove_until = $token->name;
20312:                         } elseif ($token instanceof HTMLPurifier_Token_Empty) {
20313:                             
20314:                         } else {
20315:                             $remove_until = false;
20316:                         }
20317:                         if ($e) {
20318:                             $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed');
20319:                         }
20320:                     } else {
20321:                         if ($e) {
20322:                             $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed');
20323:                         }
20324:                     }
20325:                     continue;
20326:                 }
20327:             } elseif ($token instanceof HTMLPurifier_Token_Comment) {
20328:                 
20329:                 if ($textify_comments !== false) {
20330:                     $data = $token->data;
20331:                     $token = new HTMLPurifier_Token_Text($data);
20332:                 } elseif ($trusted || $check_comments) {
20333:                     
20334:                     $trailing_hyphen = false;
20335:                     if ($e) {
20336:                         
20337:                         if (substr($token->data, -1) == '-') {
20338:                             $trailing_hyphen = true;
20339:                         }
20340:                     }
20341:                     $token->data = rtrim($token->data, '-');
20342:                     $found_double_hyphen = false;
20343:                     while (strpos($token->data, '--') !== false) {
20344:                         $found_double_hyphen = true;
20345:                         $token->data = str_replace('--', '-', $token->data);
20346:                     }
20347:                     if ($trusted || !empty($comment_lookup[trim($token->data)]) ||
20348:                         ($comment_regexp !== null && preg_match($comment_regexp, trim($token->data)))) {
20349:                         
20350:                         if ($e) {
20351:                             if ($trailing_hyphen) {
20352:                                 $e->send(
20353:                                     E_NOTICE,
20354:                                     'Strategy_RemoveForeignElements: Trailing hyphen in comment removed'
20355:                                 );
20356:                             }
20357:                             if ($found_double_hyphen) {
20358:                                 $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed');
20359:                             }
20360:                         }
20361:                     } else {
20362:                         if ($e) {
20363:                             $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');
20364:                         }
20365:                         continue;
20366:                     }
20367:                 } else {
20368:                     
20369:                     if ($e) {
20370:                         $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');
20371:                     }
20372:                     continue;
20373:                 }
20374:             } elseif ($token instanceof HTMLPurifier_Token_Text) {
20375:             } else {
20376:                 continue;
20377:             }
20378:             $result[] = $token;
20379:         }
20380:         if ($remove_until && $e) {
20381:             
20382:             $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Token removed to end', $remove_until);
20383:         }
20384:         $context->destroy('CurrentToken');
20385:         return $result;
20386:     }
20387: }
20388: 
20389: 
20390: 
20391: 
20392: 
20393: 20394: 20395: 
20396: 
20397: class HTMLPurifier_Strategy_ValidateAttributes extends HTMLPurifier_Strategy
20398: {
20399: 
20400:     20401: 20402: 20403: 20404: 20405: 
20406:     public function execute($tokens, $config, $context)
20407:     {
20408:         
20409:         $validator = new HTMLPurifier_AttrValidator();
20410: 
20411:         $token = false;
20412:         $context->register('CurrentToken', $token);
20413: 
20414:         foreach ($tokens as $key => $token) {
20415: 
20416:             
20417:             
20418:             if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) {
20419:                 continue;
20420:             }
20421: 
20422:             
20423:             if (!empty($token->armor['ValidateAttributes'])) {
20424:                 continue;
20425:             }
20426: 
20427:             
20428:             $validator->validateToken($token, $config, $context);
20429:         }
20430:         $context->destroy('CurrentToken');
20431:         return $tokens;
20432:     }
20433: }
20434: 
20435: 
20436: 
20437: 
20438: 
20439: 20440: 20441: 20442: 20443: 20444: 20445: 20446: 20447: 20448: 20449: 20450: 20451: 20452: 20453: 
20454: class HTMLPurifier_TagTransform_Font extends HTMLPurifier_TagTransform
20455: {
20456:     20457: 20458: 
20459:     public $transform_to = 'span';
20460: 
20461:     20462: 20463: 
20464:     protected $_size_lookup = array(
20465:         '0' => 'xx-small',
20466:         '1' => 'xx-small',
20467:         '2' => 'small',
20468:         '3' => 'medium',
20469:         '4' => 'large',
20470:         '5' => 'x-large',
20471:         '6' => 'xx-large',
20472:         '7' => '300%',
20473:         '-1' => 'smaller',
20474:         '-2' => '60%',
20475:         '+1' => 'larger',
20476:         '+2' => '150%',
20477:         '+3' => '200%',
20478:         '+4' => '300%'
20479:     );
20480: 
20481:     20482: 20483: 20484: 20485: 20486: 
20487:     public function transform($tag, $config, $context)
20488:     {
20489:         if ($tag instanceof HTMLPurifier_Token_End) {
20490:             $new_tag = clone $tag;
20491:             $new_tag->name = $this->transform_to;
20492:             return $new_tag;
20493:         }
20494: 
20495:         $attr = $tag->attr;
20496:         $prepend_style = '';
20497: 
20498:         
20499:         if (isset($attr['color'])) {
20500:             $prepend_style .= 'color:' . $attr['color'] . ';';
20501:             unset($attr['color']);
20502:         }
20503: 
20504:         
20505:         if (isset($attr['face'])) {
20506:             $prepend_style .= 'font-family:' . $attr['face'] . ';';
20507:             unset($attr['face']);
20508:         }
20509: 
20510:         
20511:         if (isset($attr['size'])) {
20512:             
20513:             if ($attr['size'] !== '') {
20514:                 if ($attr['size']{0} == '+' || $attr['size']{0} == '-') {
20515:                     $size = (int)$attr['size'];
20516:                     if ($size < -2) {
20517:                         $attr['size'] = '-2';
20518:                     }
20519:                     if ($size > 4) {
20520:                         $attr['size'] = '+4';
20521:                     }
20522:                 } else {
20523:                     $size = (int)$attr['size'];
20524:                     if ($size > 7) {
20525:                         $attr['size'] = '7';
20526:                     }
20527:                 }
20528:             }
20529:             if (isset($this->_size_lookup[$attr['size']])) {
20530:                 $prepend_style .= 'font-size:' .
20531:                     $this->_size_lookup[$attr['size']] . ';';
20532:             }
20533:             unset($attr['size']);
20534:         }
20535: 
20536:         if ($prepend_style) {
20537:             $attr['style'] = isset($attr['style']) ?
20538:                 $prepend_style . $attr['style'] :
20539:                 $prepend_style;
20540:         }
20541: 
20542:         $new_tag = clone $tag;
20543:         $new_tag->name = $this->transform_to;
20544:         $new_tag->attr = $attr;
20545: 
20546:         return $new_tag;
20547:     }
20548: }
20549: 
20550: 
20551: 
20552: 
20553: 
20554: 20555: 20556: 20557: 20558: 
20559: class HTMLPurifier_TagTransform_Simple extends HTMLPurifier_TagTransform
20560: {
20561:     20562: 20563: 
20564:     protected $style;
20565: 
20566:     20567: 20568: 20569: 
20570:     public function __construct($transform_to, $style = null)
20571:     {
20572:         $this->transform_to = $transform_to;
20573:         $this->style = $style;
20574:     }
20575: 
20576:     20577: 20578: 20579: 20580: 20581: 
20582:     public function transform($tag, $config, $context)
20583:     {
20584:         $new_tag = clone $tag;
20585:         $new_tag->name = $this->transform_to;
20586:         if (!is_null($this->style) &&
20587:             ($new_tag instanceof HTMLPurifier_Token_Start || $new_tag instanceof HTMLPurifier_Token_Empty)
20588:         ) {
20589:             $this->prependCSS($new_tag->attr, $this->style);
20590:         }
20591:         return $new_tag;
20592:     }
20593: }
20594: 
20595: 
20596: 
20597: 
20598: 
20599: 20600: 20601: 
20602: class  extends HTMLPurifier_Token
20603: {
20604:     20605: 20606: 20607: 
20608:     public $data;
20609: 
20610:     20611: 20612: 
20613:     public $is_whitespace = true;
20614: 
20615:     20616: 20617: 20618: 20619: 20620: 20621: 
20622:     public function __construct($data, $line = null, $col = null)
20623:     {
20624:         $this->data = $data;
20625:         $this->line = $line;
20626:         $this->col = $col;
20627:     }
20628: 
20629:     public function toNode() {
20630:         return new HTMLPurifier_Node_Comment($this->data, $this->line, $this->col);
20631:     }
20632: }
20633: 
20634: 
20635: 
20636: 
20637: 
20638: 20639: 20640: 
20641: abstract class HTMLPurifier_Token_Tag extends HTMLPurifier_Token
20642: {
20643:     20644: 20645: 20646: 20647: 20648: 20649: 
20650:     public $is_tag = true;
20651: 
20652:     20653: 20654: 20655: 20656: 20657: 20658: 20659: 
20660:     public $name;
20661: 
20662:     20663: 20664: 20665: 
20666:     public $attr = array();
20667: 
20668:     20669: 20670: 20671: 20672: 20673: 20674: 20675: 20676: 
20677:     public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array())
20678:     {
20679:         $this->name = ctype_lower($name) ? $name : strtolower($name);
20680:         foreach ($attr as $key => $value) {
20681:             
20682:             if (!ctype_lower($key)) {
20683:                 $new_key = strtolower($key);
20684:                 if (!isset($attr[$new_key])) {
20685:                     $attr[$new_key] = $attr[$key];
20686:                 }
20687:                 if ($new_key !== $key) {
20688:                     unset($attr[$key]);
20689:                 }
20690:             }
20691:         }
20692:         $this->attr = $attr;
20693:         $this->line = $line;
20694:         $this->col = $col;
20695:         $this->armor = $armor;
20696:     }
20697: 
20698:     public function toNode() {
20699:         return new HTMLPurifier_Node_Element($this->name, $this->attr, $this->line, $this->col, $this->armor);
20700:     }
20701: }
20702: 
20703: 
20704: 
20705: 
20706: 
20707: 20708: 20709: 
20710: class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag
20711: {
20712:     public function toNode() {
20713:         $n = parent::toNode();
20714:         $n->empty = true;
20715:         return $n;
20716:     }
20717: }
20718: 
20719: 
20720: 
20721: 
20722: 
20723: 20724: 20725: 20726: 20727: 20728: 20729: 
20730: class HTMLPurifier_Token_End extends HTMLPurifier_Token_Tag
20731: {
20732:     20733: 20734: 20735: 20736: 
20737:     public $start;
20738: 
20739:     public function toNode() {
20740:         throw new Exception("HTMLPurifier_Token_End->toNode not supported!");
20741:     }
20742: }
20743: 
20744: 
20745: 
20746: 
20747: 
20748: 20749: 20750: 
20751: class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag
20752: {
20753: }
20754: 
20755: 
20756: 
20757: 
20758: 
20759: 20760: 20761: 20762: 20763: 20764: 20765: 20766: 20767: 
20768: class HTMLPurifier_Token_Text extends HTMLPurifier_Token
20769: {
20770: 
20771:     20772: 20773: 
20774:     public $name = '#PCDATA';
20775:     
20776: 
20777:     20778: 20779: 
20780:     public $data;
20781:     
20782: 
20783:     20784: 20785: 
20786:     public $is_whitespace;
20787: 
20788:     
20789: 
20790:     20791: 20792: 20793: 20794: 20795: 
20796:     public function __construct($data, $line = null, $col = null)
20797:     {
20798:         $this->data = $data;
20799:         $this->is_whitespace = ctype_space($data);
20800:         $this->line = $line;
20801:         $this->col = $col;
20802:     }
20803: 
20804:     public function toNode() {
20805:         return new HTMLPurifier_Node_Text($this->data, $this->is_whitespace, $this->line, $this->col);
20806:     }
20807: }
20808: 
20809: 
20810: 
20811: 
20812: 
20813: class HTMLPurifier_URIFilter_DisableExternal extends HTMLPurifier_URIFilter
20814: {
20815:     20816: 20817: 
20818:     public $name = 'DisableExternal';
20819: 
20820:     20821: 20822: 
20823:     protected $ourHostParts = false;
20824: 
20825:     20826: 20827: 20828: 
20829:     public function prepare($config)
20830:     {
20831:         $our_host = $config->getDefinition('URI')->host;
20832:         if ($our_host !== null) {
20833:             $this->ourHostParts = array_reverse(explode('.', $our_host));
20834:         }
20835:     }
20836: 
20837:     20838: 20839: 20840: 20841: 20842: 
20843:     public function filter(&$uri, $config, $context)
20844:     {
20845:         if (is_null($uri->host)) {
20846:             return true;
20847:         }
20848:         if ($this->ourHostParts === false) {
20849:             return false;
20850:         }
20851:         $host_parts = array_reverse(explode('.', $uri->host));
20852:         foreach ($this->ourHostParts as $i => $x) {
20853:             if (!isset($host_parts[$i])) {
20854:                 return false;
20855:             }
20856:             if ($host_parts[$i] != $this->ourHostParts[$i]) {
20857:                 return false;
20858:             }
20859:         }
20860:         return true;
20861:     }
20862: }
20863: 
20864: 
20865: 
20866: 
20867: 
20868: class HTMLPurifier_URIFilter_DisableExternalResources extends HTMLPurifier_URIFilter_DisableExternal
20869: {
20870:     20871: 20872: 
20873:     public $name = 'DisableExternalResources';
20874: 
20875:     20876: 20877: 20878: 20879: 20880: 
20881:     public function filter(&$uri, $config, $context)
20882:     {
20883:         if (!$context->get('EmbeddedURI', true)) {
20884:             return true;
20885:         }
20886:         return parent::filter($uri, $config, $context);
20887:     }
20888: }
20889: 
20890: 
20891: 
20892: 
20893: 
20894: class HTMLPurifier_URIFilter_DisableResources extends HTMLPurifier_URIFilter
20895: {
20896:     20897: 20898: 
20899:     public $name = 'DisableResources';
20900: 
20901:     20902: 20903: 20904: 20905: 20906: 
20907:     public function filter(&$uri, $config, $context)
20908:     {
20909:         return !$context->get('EmbeddedURI', true);
20910:     }
20911: }
20912: 
20913: 
20914: 
20915: 
20916: 
20917: 
20918: 
20919: 
20920: 
20921: class HTMLPurifier_URIFilter_HostBlacklist extends HTMLPurifier_URIFilter
20922: {
20923:     20924: 20925: 
20926:     public $name = 'HostBlacklist';
20927: 
20928:     20929: 20930: 
20931:     protected $blacklist = array();
20932: 
20933:     20934: 20935: 20936: 
20937:     public function prepare($config)
20938:     {
20939:         $this->blacklist = $config->get('URI.HostBlacklist');
20940:         return true;
20941:     }
20942: 
20943:     20944: 20945: 20946: 20947: 20948: 
20949:     public function filter(&$uri, $config, $context)
20950:     {
20951:         foreach ($this->blacklist as $blacklisted_host_fragment) {
20952:             if (strpos($uri->host, $blacklisted_host_fragment) !== false) {
20953:                 return false;
20954:             }
20955:         }
20956:         return true;
20957:     }
20958: }
20959: 
20960: 
20961: 
20962: 
20963: 
20964: 
20965: 
20966: class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter
20967: {
20968:     20969: 20970: 
20971:     public $name = 'MakeAbsolute';
20972: 
20973:     20974: 20975: 
20976:     protected $base;
20977: 
20978:     20979: 20980: 
20981:     protected $basePathStack = array();
20982: 
20983:     20984: 20985: 20986: 
20987:     public function prepare($config)
20988:     {
20989:         $def = $config->getDefinition('URI');
20990:         $this->base = $def->base;
20991:         if (is_null($this->base)) {
20992:             trigger_error(
20993:                 'URI.MakeAbsolute is being ignored due to lack of ' .
20994:                 'value for URI.Base configuration',
20995:                 E_USER_WARNING
20996:             );
20997:             return false;
20998:         }
20999:         $this->base->fragment = null; 
21000:         $stack = explode('/', $this->base->path);
21001:         array_pop($stack); 
21002:         $stack = $this->_collapseStack($stack); 
21003:         $this->basePathStack = $stack;
21004:         return true;
21005:     }
21006: 
21007:     21008: 21009: 21010: 21011: 21012: 
21013:     public function filter(&$uri, $config, $context)
21014:     {
21015:         if (is_null($this->base)) {
21016:             return true;
21017:         } 
21018:         if ($uri->path === '' && is_null($uri->scheme) &&
21019:             is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment)) {
21020:             
21021:             $uri = clone $this->base;
21022:             return true;
21023:         }
21024:         if (!is_null($uri->scheme)) {
21025:             
21026:             if (!is_null($uri->host)) {
21027:                 return true;
21028:             }
21029:             $scheme_obj = $uri->getSchemeObj($config, $context);
21030:             if (!$scheme_obj) {
21031:                 
21032:                 return false;
21033:             }
21034:             if (!$scheme_obj->hierarchical) {
21035:                 
21036:                 return true;
21037:             }
21038:             
21039:         }
21040:         if (!is_null($uri->host)) {
21041:             
21042:             return true;
21043:         }
21044:         if ($uri->path === '') {
21045:             $uri->path = $this->base->path;
21046:         } elseif ($uri->path[0] !== '/') {
21047:             
21048:             $stack = explode('/', $uri->path);
21049:             $new_stack = array_merge($this->basePathStack, $stack);
21050:             if ($new_stack[0] !== '' && !is_null($this->base->host)) {
21051:                 array_unshift($new_stack, '');
21052:             }
21053:             $new_stack = $this->_collapseStack($new_stack);
21054:             $uri->path = implode('/', $new_stack);
21055:         } else {
21056:             
21057:             $uri->path = implode('/', $this->_collapseStack(explode('/', $uri->path)));
21058:         }
21059:         
21060:         $uri->scheme = $this->base->scheme;
21061:         if (is_null($uri->userinfo)) {
21062:             $uri->userinfo = $this->base->userinfo;
21063:         }
21064:         if (is_null($uri->host)) {
21065:             $uri->host = $this->base->host;
21066:         }
21067:         if (is_null($uri->port)) {
21068:             $uri->port = $this->base->port;
21069:         }
21070:         return true;
21071:     }
21072: 
21073:     21074: 21075: 21076: 21077: 
21078:     private function _collapseStack($stack)
21079:     {
21080:         $result = array();
21081:         $is_folder = false;
21082:         for ($i = 0; isset($stack[$i]); $i++) {
21083:             $is_folder = false;
21084:             
21085:             if ($stack[$i] == '' && $i && isset($stack[$i + 1])) {
21086:                 continue;
21087:             }
21088:             if ($stack[$i] == '..') {
21089:                 if (!empty($result)) {
21090:                     $segment = array_pop($result);
21091:                     if ($segment === '' && empty($result)) {
21092:                         
21093:                         
21094:                         $result[] = '';
21095:                     } elseif ($segment === '..') {
21096:                         $result[] = '..'; 
21097:                     }
21098:                 } else {
21099:                     
21100:                     $result[] = '..';
21101:                 }
21102:                 $is_folder = true;
21103:                 continue;
21104:             }
21105:             if ($stack[$i] == '.') {
21106:                 
21107:                 $is_folder = true;
21108:                 continue;
21109:             }
21110:             $result[] = $stack[$i];
21111:         }
21112:         if ($is_folder) {
21113:             $result[] = '';
21114:         }
21115:         return $result;
21116:     }
21117: }
21118: 
21119: 
21120: 
21121: 
21122: 
21123: class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter
21124: {
21125:     21126: 21127: 
21128:     public $name = 'Munge';
21129: 
21130:     21131: 21132: 
21133:     public $post = true;
21134: 
21135:     21136: 21137: 
21138:     private $target;
21139: 
21140:     21141: 21142: 
21143:     private $parser;
21144: 
21145:     21146: 21147: 
21148:     private $doEmbed;
21149: 
21150:     21151: 21152: 
21153:     private $secretKey;
21154: 
21155:     21156: 21157: 
21158:     protected $replace = array();
21159: 
21160:     21161: 21162: 21163: 
21164:     public function prepare($config)
21165:     {
21166:         $this->target = $config->get('URI.' . $this->name);
21167:         $this->parser = new HTMLPurifier_URIParser();
21168:         $this->doEmbed = $config->get('URI.MungeResources');
21169:         $this->secretKey = $config->get('URI.MungeSecretKey');
21170:         if ($this->secretKey && !function_exists('hash_hmac')) {
21171:             throw new Exception("Cannot use %URI.MungeSecretKey without hash_hmac support.");
21172:         }
21173:         return true;
21174:     }
21175: 
21176:     21177: 21178: 21179: 21180: 21181: 
21182:     public function filter(&$uri, $config, $context)
21183:     {
21184:         if ($context->get('EmbeddedURI', true) && !$this->doEmbed) {
21185:             return true;
21186:         }
21187: 
21188:         $scheme_obj = $uri->getSchemeObj($config, $context);
21189:         if (!$scheme_obj) {
21190:             return true;
21191:         } 
21192:         if (!$scheme_obj->browsable) {
21193:             return true;
21194:         } 
21195:         if ($uri->isBenign($config, $context)) {
21196:             return true;
21197:         } 
21198: 
21199:         $this->makeReplace($uri, $config, $context);
21200:         $this->replace = array_map('rawurlencode', $this->replace);
21201: 
21202:         $new_uri = strtr($this->target, $this->replace);
21203:         $new_uri = $this->parser->parse($new_uri);
21204:         
21205:         
21206:         if ($uri->host === $new_uri->host) {
21207:             return true;
21208:         }
21209:         $uri = $new_uri; 
21210:         return true;
21211:     }
21212: 
21213:     21214: 21215: 21216: 21217: 
21218:     protected function makeReplace($uri, $config, $context)
21219:     {
21220:         $string = $uri->toString();
21221:         
21222:         $this->replace['%s'] = $string;
21223:         $this->replace['%r'] = $context->get('EmbeddedURI', true);
21224:         $token = $context->get('CurrentToken', true);
21225:         $this->replace['%n'] = $token ? $token->name : null;
21226:         $this->replace['%m'] = $context->get('CurrentAttr', true);
21227:         $this->replace['%p'] = $context->get('CurrentCSSProperty', true);
21228:         
21229:         if ($this->secretKey) {
21230:             $this->replace['%t'] = hash_hmac("sha256", $string, $this->secretKey);
21231:         }
21232:     }
21233: }
21234: 
21235: 
21236: 
21237: 
21238: 
21239: 21240: 21241: 21242: 21243: 21244: 
21245: class HTMLPurifier_URIFilter_SafeIframe extends HTMLPurifier_URIFilter
21246: {
21247:     21248: 21249: 
21250:     public $name = 'SafeIframe';
21251: 
21252:     21253: 21254: 
21255:     public $always_load = true;
21256: 
21257:     21258: 21259: 
21260:     protected $regexp = null;
21261: 
21262:     
21263:     
21264:     
21265:     21266: 21267: 21268: 
21269:     public function prepare($config)
21270:     {
21271:         $this->regexp = $config->get('URI.SafeIframeRegexp');
21272:         return true;
21273:     }
21274: 
21275:     21276: 21277: 21278: 21279: 21280: 
21281:     public function filter(&$uri, $config, $context)
21282:     {
21283:         
21284:         if (!$config->get('HTML.SafeIframe')) {
21285:             return true;
21286:         }
21287:         
21288:         if (!$context->get('EmbeddedURI', true)) {
21289:             return true;
21290:         }
21291:         $token = $context->get('CurrentToken', true);
21292:         if (!($token && $token->name == 'iframe')) {
21293:             return true;
21294:         }
21295:         
21296:         if ($this->regexp === null) {
21297:             return false;
21298:         }
21299:         
21300:         return preg_match($this->regexp, $uri->toString());
21301:     }
21302: }
21303: 
21304: 
21305: 
21306: 
21307: 
21308: 21309: 21310: 
21311: class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme
21312: {
21313:     21314: 21315: 
21316:     public $browsable = true;
21317: 
21318:     21319: 21320: 
21321:     public $allowed_types = array(
21322:         
21323:         
21324:         'image/jpeg' => true,
21325:         'image/gif' => true,
21326:         'image/png' => true,
21327:     );
21328:     
21329:     
21330:     21331: 21332: 
21333:     public $may_omit_host = true;
21334: 
21335:     21336: 21337: 21338: 21339: 21340: 
21341:     public function doValidate(&$uri, $config, $context)
21342:     {
21343:         $result = explode(',', $uri->path, 2);
21344:         $is_base64 = false;
21345:         $charset = null;
21346:         $content_type = null;
21347:         if (count($result) == 2) {
21348:             list($metadata, $data) = $result;
21349:             
21350:             $metas = explode(';', $metadata);
21351:             while (!empty($metas)) {
21352:                 $cur = array_shift($metas);
21353:                 if ($cur == 'base64') {
21354:                     $is_base64 = true;
21355:                     break;
21356:                 }
21357:                 if (substr($cur, 0, 8) == 'charset=') {
21358:                     
21359:                     
21360:                     if ($charset !== null) {
21361:                         continue;
21362:                     } 
21363:                     $charset = substr($cur, 8); 
21364:                 } else {
21365:                     if ($content_type !== null) {
21366:                         continue;
21367:                     } 
21368:                     $content_type = $cur;
21369:                 }
21370:             }
21371:         } else {
21372:             $data = $result[0];
21373:         }
21374:         if ($content_type !== null && empty($this->allowed_types[$content_type])) {
21375:             return false;
21376:         }
21377:         if ($charset !== null) {
21378:             
21379:             $charset = null;
21380:         }
21381:         $data = rawurldecode($data);
21382:         if ($is_base64) {
21383:             $raw_data = base64_decode($data);
21384:         } else {
21385:             $raw_data = $data;
21386:         }
21387:         
21388:         
21389:         $file = tempnam("/tmp", "");
21390:         file_put_contents($file, $raw_data);
21391:         if (function_exists('exif_imagetype')) {
21392:             $image_code = exif_imagetype($file);
21393:             unlink($file);
21394:         } elseif (function_exists('getimagesize')) {
21395:             set_error_handler(array($this, 'muteErrorHandler'));
21396:             $info = getimagesize($file);
21397:             restore_error_handler();
21398:             unlink($file);
21399:             if ($info == false) {
21400:                 return false;
21401:             }
21402:             $image_code = $info[2];
21403:         } else {
21404:             trigger_error("could not find exif_imagetype or getimagesize functions", E_USER_ERROR);
21405:         }
21406:         $real_content_type = image_type_to_mime_type($image_code);
21407:         if ($real_content_type != $content_type) {
21408:             
21409:             
21410:             if (empty($this->allowed_types[$real_content_type])) {
21411:                 return false;
21412:             }
21413:             $content_type = $real_content_type;
21414:         }
21415:         
21416:         $uri->userinfo = null;
21417:         $uri->host = null;
21418:         $uri->port = null;
21419:         $uri->fragment = null;
21420:         $uri->query = null;
21421:         $uri->path = "$content_type;base64," . base64_encode($raw_data);
21422:         return true;
21423:     }
21424: 
21425:     21426: 21427: 21428: 
21429:     public function muteErrorHandler($errno, $errstr)
21430:     {
21431:     }
21432: }
21433: 
21434: 
21435: 
21436: 21437: 21438: 
21439: class HTMLPurifier_URIScheme_file extends HTMLPurifier_URIScheme
21440: {
21441:     21442: 21443: 21444: 21445: 
21446:     public $browsable = false;
21447: 
21448:     21449: 21450: 21451: 21452: 21453: 21454: 21455: 
21456:     public $may_omit_host = true;
21457: 
21458:     21459: 21460: 21461: 21462: 21463: 
21464:     public function doValidate(&$uri, $config, $context)
21465:     {
21466:         
21467:         $uri->userinfo = null;
21468:         
21469:         $uri->port = null;
21470:         
21471:         
21472:         $uri->query = null;
21473:         return true;
21474:     }
21475: }
21476: 
21477: 
21478: 
21479: 
21480: 
21481: 21482: 21483: 
21484: class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme
21485: {
21486:     21487: 21488: 
21489:     public $default_port = 21;
21490: 
21491:     21492: 21493: 
21494:     public $browsable = true; 
21495: 
21496:     21497: 21498: 
21499:     public $hierarchical = true;
21500: 
21501:     21502: 21503: 21504: 21505: 21506: 
21507:     public function doValidate(&$uri, $config, $context)
21508:     {
21509:         $uri->query = null;
21510: 
21511:         
21512:         $semicolon_pos = strrpos($uri->path, ';'); 
21513:         if ($semicolon_pos !== false) {
21514:             $type = substr($uri->path, $semicolon_pos + 1); 
21515:             $uri->path = substr($uri->path, 0, $semicolon_pos);
21516:             $type_ret = '';
21517:             if (strpos($type, '=') !== false) {
21518:                 
21519:                 list($key, $typecode) = explode('=', $type, 2);
21520:                 if ($key !== 'type') {
21521:                     
21522:                     $uri->path .= '%3B' . $type;
21523:                 } elseif ($typecode === 'a' || $typecode === 'i' || $typecode === 'd') {
21524:                     $type_ret = ";type=$typecode";
21525:                 }
21526:             } else {
21527:                 $uri->path .= '%3B' . $type;
21528:             }
21529:             $uri->path = str_replace(';', '%3B', $uri->path);
21530:             $uri->path .= $type_ret;
21531:         }
21532:         return true;
21533:     }
21534: }
21535: 
21536: 
21537: 
21538: 
21539: 
21540: 21541: 21542: 
21543: class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme
21544: {
21545:     21546: 21547: 
21548:     public $default_port = 80;
21549: 
21550:     21551: 21552: 
21553:     public $browsable = true;
21554: 
21555:     21556: 21557: 
21558:     public $hierarchical = true;
21559: 
21560:     21561: 21562: 21563: 21564: 21565: 
21566:     public function doValidate(&$uri, $config, $context)
21567:     {
21568:         $uri->userinfo = null;
21569:         return true;
21570:     }
21571: }
21572: 
21573: 
21574: 
21575: 
21576: 
21577: 21578: 21579: 
21580: class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http
21581: {
21582:     21583: 21584: 
21585:     public $default_port = 443;
21586:     21587: 21588: 
21589:     public $secure = true;
21590: }
21591: 
21592: 
21593: 
21594: 
21595: 
21596: 
21597: 
21598: 
21599: 21600: 21601: 21602: 21603: 
21604: 
21605: class HTMLPurifier_URIScheme_mailto extends HTMLPurifier_URIScheme
21606: {
21607:     21608: 21609: 
21610:     public $browsable = false;
21611: 
21612:     21613: 21614: 
21615:     public $may_omit_host = true;
21616: 
21617:     21618: 21619: 21620: 21621: 21622: 
21623:     public function doValidate(&$uri, $config, $context)
21624:     {
21625:         $uri->userinfo = null;
21626:         $uri->host     = null;
21627:         $uri->port     = null;
21628:         
21629:         return true;
21630:     }
21631: }
21632: 
21633: 
21634: 
21635: 
21636: 
21637: 21638: 21639: 
21640: class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme
21641: {
21642:     21643: 21644: 
21645:     public $browsable = false;
21646: 
21647:     21648: 21649: 
21650:     public $may_omit_host = true;
21651: 
21652:     21653: 21654: 21655: 21656: 21657: 
21658:     public function doValidate(&$uri, $config, $context)
21659:     {
21660:         $uri->userinfo = null;
21661:         $uri->host = null;
21662:         $uri->port = null;
21663:         $uri->query = null;
21664:         
21665:         return true;
21666:     }
21667: }
21668: 
21669: 
21670: 
21671: 
21672: 
21673: 21674: 21675: 
21676: class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme
21677: {
21678:     21679: 21680: 
21681:     public $default_port = 119;
21682: 
21683:     21684: 21685: 
21686:     public $browsable = false;
21687: 
21688:     21689: 21690: 21691: 21692: 21693: 
21694:     public function doValidate(&$uri, $config, $context)
21695:     {
21696:         $uri->userinfo = null;
21697:         $uri->query = null;
21698:         return true;
21699:     }
21700: }
21701: 
21702: 
21703: 
21704: 
21705: 
21706: 21707: 21708: 21709: 21710: 
21711: class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser
21712: {
21713:     21714: 21715: 21716: 21717: 21718: 21719: 
21720:     protected function parseImplementation($var, $type, $allow_null)
21721:     {
21722:         if ($allow_null && $var === null) {
21723:             return null;
21724:         }
21725:         switch ($type) {
21726:             
21727:             
21728:             
21729:             case self::MIXED:
21730:             case self::ISTRING:
21731:             case self::STRING:
21732:             case self::TEXT:
21733:             case self::ITEXT:
21734:                 return $var;
21735:             case self::INT:
21736:                 if (is_string($var) && ctype_digit($var)) {
21737:                     $var = (int)$var;
21738:                 }
21739:                 return $var;
21740:             case self::FLOAT:
21741:                 if ((is_string($var) && is_numeric($var)) || is_int($var)) {
21742:                     $var = (float)$var;
21743:                 }
21744:                 return $var;
21745:             case self::BOOL:
21746:                 if (is_int($var) && ($var === 0 || $var === 1)) {
21747:                     $var = (bool)$var;
21748:                 } elseif (is_string($var)) {
21749:                     if ($var == 'on' || $var == 'true' || $var == '1') {
21750:                         $var = true;
21751:                     } elseif ($var == 'off' || $var == 'false' || $var == '0') {
21752:                         $var = false;
21753:                     } else {
21754:                         throw new HTMLPurifier_VarParserException("Unrecognized value '$var' for $type");
21755:                     }
21756:                 }
21757:                 return $var;
21758:             case self::ALIST:
21759:             case self::HASH:
21760:             case self::LOOKUP:
21761:                 if (is_string($var)) {
21762:                     
21763:                     
21764:                     
21765:                     if ($var == '') {
21766:                         return array();
21767:                     }
21768:                     if (strpos($var, "\n") === false && strpos($var, "\r") === false) {
21769:                         
21770:                         
21771:                         $var = explode(',', $var);
21772:                     } else {
21773:                         $var = preg_split('/(,|[\n\r]+)/', $var);
21774:                     }
21775:                     
21776:                     foreach ($var as $i => $j) {
21777:                         $var[$i] = trim($j);
21778:                     }
21779:                     if ($type === self::HASH) {
21780:                         
21781:                         $nvar = array();
21782:                         foreach ($var as $keypair) {
21783:                             $c = explode(':', $keypair, 2);
21784:                             if (!isset($c[1])) {
21785:                                 continue;
21786:                             }
21787:                             $nvar[trim($c[0])] = trim($c[1]);
21788:                         }
21789:                         $var = $nvar;
21790:                     }
21791:                 }
21792:                 if (!is_array($var)) {
21793:                     break;
21794:                 }
21795:                 $keys = array_keys($var);
21796:                 if ($keys === array_keys($keys)) {
21797:                     if ($type == self::ALIST) {
21798:                         return $var;
21799:                     } elseif ($type == self::LOOKUP) {
21800:                         $new = array();
21801:                         foreach ($var as $key) {
21802:                             $new[$key] = true;
21803:                         }
21804:                         return $new;
21805:                     } else {
21806:                         break;
21807:                     }
21808:                 }
21809:                 if ($type === self::ALIST) {
21810:                     trigger_error("Array list did not have consecutive integer indexes", E_USER_WARNING);
21811:                     return array_values($var);
21812:                 }
21813:                 if ($type === self::LOOKUP) {
21814:                     foreach ($var as $key => $value) {
21815:                         if ($value !== true) {
21816:                             trigger_error(
21817:                                 "Lookup array has non-true value at key '$key'; " .
21818:                                 "maybe your input array was not indexed numerically",
21819:                                 E_USER_WARNING
21820:                             );
21821:                         }
21822:                         $var[$key] = true;
21823:                     }
21824:                 }
21825:                 return $var;
21826:             default:
21827:                 $this->errorInconsistent(__CLASS__, $type);
21828:         }
21829:         $this->errorGeneric($var, $type);
21830:     }
21831: }
21832: 
21833: 
21834: 
21835: 
21836: 
21837: 21838: 21839: 21840: 21841: 
21842: class HTMLPurifier_VarParser_Native extends HTMLPurifier_VarParser
21843: {
21844: 
21845:     21846: 21847: 21848: 21849: 21850: 
21851:     protected function parseImplementation($var, $type, $allow_null)
21852:     {
21853:         return $this->evalExpression($var);
21854:     }
21855: 
21856:     21857: 21858: 21859: 21860: 
21861:     protected function evalExpression($expr)
21862:     {
21863:         $var = null;
21864:         $result = eval("\$var = $expr;");
21865:         if ($result === false) {
21866:             throw new HTMLPurifier_VarParserException("Fatal error in evaluated code");
21867:         }
21868:         return $var;
21869:     }
21870: }
21871: 
21872: 
21873: 
21874: