aboutsummaryrefslogtreecommitdiffstats
path: root/lib/htmlpurifier/tests/HTMLPurifier
diff options
context:
space:
mode:
Diffstat (limited to 'lib/htmlpurifier/tests/HTMLPurifier')
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrCollectionsTest.php134
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/AlphaValueTest.php28
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BackgroundPositionTest.php68
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BackgroundTest.php23
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BorderTest.php21
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ColorTest.php41
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/CompositeTest.php81
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FilterTest.php29
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FontFamilyTest.php52
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FontTest.php34
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ImportantDecoratorTest.php49
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/LengthTest.php48
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ListStyleTest.php35
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/MultipleTest.php28
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/NumberTest.php51
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/PercentageTest.php24
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/TextDecorationTest.php27
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/URITest.php29
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSSTest.php164
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/EnumTest.php37
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/BoolTest.php22
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/ClassTest.php48
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/ColorTest.php20
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/FrameTargetTest.php28
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/IDTest.php108
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/LengthTest.php32
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/LinkTypesTest.php21
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/MultiLengthTest.php28
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/NmtokensTest.php35
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/PixelsTest.php45
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/IntegerTest.php61
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/LangTest.php85
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/SwitchTest.php34
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/TextTest.php17
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/Email/SimpleCheckTest.php13
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/EmailHarness.php31
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/HostTest.php53
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/IPv4Test.php25
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/IPv6Test.php43
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URITest.php146
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDefHarness.php27
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrDefTest.php32
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BackgroundTest.php40
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BdoDirTest.php30
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BgColorTest.php44
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BoolToCSSTest.php38
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BorderTest.php38
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/EnumToCSSTest.php73
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/ImgRequiredTest.php55
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/ImgSpaceTest.php55
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/InputTest.php94
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/LangTest.php46
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/LengthTest.php45
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/NameSyncTest.php40
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/NameTest.php31
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrTransformHarness.php13
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrTransformTest.php45
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrTypesTest.php26
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/AttrValidator_ErrorsTest.php66
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ChildDef/ChameleonTest.php40
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ChildDef/CustomTest.php89
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ChildDef/ListTest.php50
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ChildDef/OptionalTest.php33
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ChildDef/RequiredTest.php73
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ChildDef/StrictBlockquoteTest.php83
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ChildDef/TableTest.php75
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ChildDefHarness.php16
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ComplexHarness.php106
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/InterchangeTest.php21
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/aliasesAliasCollision.vtest13
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/aliasesDirectiveCollision.vtest12
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/allowedIsString.vtest7
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/allowedNotEmpty.vtest7
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultIsAllowed.vtest7
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultNullWithAllowed.vtest5
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultType.vtest6
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/descriptionNotEmpty.vtest5
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/ignoreNamespace.vtest3
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeDefined.vtest5
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeExists.vtest6
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeWithAllowedIsStringType.vtest7
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeWithValueAliasesIsStringType.vtest7
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/unique.vtest11
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesAliasIsString.vtest7
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesAliasNotAllowed.vtest8
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesNotAliasSelf.vtest7
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesRealAllowed.vtest8
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesRealIsString.vtest7
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorAtomTest.php92
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorTest.php101
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorTestCase.php44
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigSchemaTest.php99
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-create.ini4
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-finalize.ini4
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-loadIni.ini6
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ConfigTest.php557
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ContextTest.php84
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/Decorator/CleanupTest.php56
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/Decorator/MemoryTest.php70
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/DecoratorHarness.php23
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/DecoratorTest.php42
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/SerializerTest.php225
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/SerializerTest/README3
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheFactoryTest.php70
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheHarness.php34
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheTest.php32
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/DefinitionTest.php20
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/DefinitionTestable.php8
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/DoctypeRegistryTest.php77
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ElementDefTest.php91
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/EncoderTest.php215
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/EntityLookupTest.php27
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/EntityParserTest.php85
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ErrorCollectorEMock.php48
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ErrorCollectorTest.php157
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/ErrorsHarness.php38
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Filter/ExtractStyleBlocksTest.php231
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/GeneratorTest.php288
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLDefinitionTest.php355
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/FormsTest.php155
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ImageTest.php55
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/NameTest.php32
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/NofollowTest.php26
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ObjectTest.php38
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ProprietaryTest.php30
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/RubyTest.php55
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/SafeEmbedTest.php41
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/SafeObjectTest.php49
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ScriptingTest.php55
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/TargetBlankTest.php20
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/TidyTest.php224
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleHarness.php11
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleManagerTest.php120
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleTest.php146
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT.php33
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/allowed-preserve.htmlt8
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/allowed-remove.htmlt8
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/basic.htmlt5
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/blacklist-preserve.htmlt6
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/blacklist-remove.htmlt8
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/css-allowed-preserve.htmlt5
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/css-allowed-remove.htmlt7
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/disable-uri.htmlt6
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/double-youtube.htmlt6
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/empty.htmlt6
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/file-uri.htmlt5
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-default.htmlt5
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-enabled.htmlt6
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-img.htmlt8
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-name-mix.htmlt11
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/inline-list-loop.htmlt5
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/inline-wraps-block.htmlt5
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/list-nesting.htmlt5
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/munge-extra.htmlt11
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/munge.htmlt52
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/name.htmlt6
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-googlemaps.htmlt8
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-invalid.htmlt7
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-youtube.htmlt8
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe.htmlt14
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-object-embed-munge.htmlt10
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-object-embed.htmlt8
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-bare.htmlt9
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-cdata.htmlt11
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-comment.htmlt11
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-dbl-comment.htmlt11
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-ideal.htmlt11
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/secure-munge.htmlt10
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/shift-jis-preserve-yen.htmlt8
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/shift-jis-remove-yen.htmlt9
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-blockquote-with-inline.htmlt7
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-blockquote.htmlt7
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-underline.htmlt7
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/tidy-background.htmlt5
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments-required.htmlt6
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments-table.htmlt5
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments.htmlt5
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/HTMLT/whitespace-preserve.htmlt3
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Harness.php90
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/IDAccumulatorTest.php39
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Injector/AutoParagraphTest.php515
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Injector/DisplayLinkURITest.php33
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Injector/LinkifyTest.php50
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Injector/PurifierLinkifyTest.php59
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Injector/RemoveEmptyTest.php80
-rwxr-xr-xlib/htmlpurifier/tests/HTMLPurifier/Injector/RemoveSpansWithoutAttributesTest.php99
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Injector/SafeObjectTest.php88
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/InjectorHarness.php13
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/LanguageFactoryTest.php70
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/LanguageTest.php79
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/LengthTest.php73
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Lexer/DirectLexTest.php130
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Lexer/DirectLex_ErrorsTest.php58
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/LexerTest.php752
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/domxml.phpt15
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/func.phpt9
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/kses/basic.phpt15
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/_autoload.inc12
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/_no-autoload.inc17
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-includes.phpt12
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-autoload.phpt28
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-spl-autoload-default.phpt25
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-spl-autoload.phpt43
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-without-spl-autoload.phpt19
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-without-spl-with-autoload.phpt21
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto.phpt11
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/error-auto-with-spl-nonstatic-autoload.phpt32
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/path-includes-autoload.phpt14
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/path-includes.phpt12
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/safe-includes.phpt12
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone-autoload.phpt12
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone-with-prefix.phpt15
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone.phpt13
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/stub.phpt6
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/utf8.phpt9
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PHPT/ze1_compatibility_mode.phpt14
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PercentEncoderTest.php63
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/PropertyListTest.php93
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/SimpleTest/Reporter.php60
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/SimpleTest/TextReporter.php21
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Strategy/CompositeTest.php66
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Strategy/CoreTest.php45
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Strategy/ErrorsHarness.php18
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Strategy/FixNestingTest.php144
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Strategy/FixNesting_ErrorsTest.php41
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndInsertInjector.php18
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndInsertInjectorTest.php38
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndRewindInjector.php29
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndRewindInjectorTest.php33
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/SkipInjector.php12
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/SkipInjectorTest.php27
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormedTest.php149
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed_ErrorsTest.php62
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed_InjectorTest.php149
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElementsTest.php115
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElements_ErrorsTest.php71
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElements_TidyTest.php45
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributesTest.php232
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributes_IDTest.php63
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributes_TidyTest.php351
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/StrategyHarness.php15
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/AppendMultiline.txt5
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Default.txt2
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Multi.txt19
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/OverrideSingle.txt3
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Simple.txt10
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/StringHashParserTest.php89
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/StringHashTest.php20
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/TagTransformTest.php179
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/TokenFactoryTest.php17
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/TokenTest.php34
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/URIDefinitionTest.php62
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableExternalResourcesTest.php23
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableExternalTest.php53
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableResourcesTest.php24
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/URIFilter/HostBlacklistTest.php29
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/URIFilter/MakeAbsoluteTest.php146
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/URIFilter/MungeTest.php145
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/URIFilterHarness.php15
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/URIHarness.php31
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/URIParserTest.php145
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/URISchemeRegistryTest.php49
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/URISchemeTest.php188
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/URITest.php201
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/UnitConverterTest.php131
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/VarParser/FlexibleTest.php66
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/VarParser/NativeTest.php12
-rw-r--r--lib/htmlpurifier/tests/HTMLPurifier/VarParserHarness.php33
268 files changed, 14385 insertions, 0 deletions
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrCollectionsTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrCollectionsTest.php
new file mode 100644
index 000000000..227bc9535
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrCollectionsTest.php
@@ -0,0 +1,134 @@
+<?php
+
+Mock::generatePartial(
+ 'HTMLPurifier_AttrCollections',
+ 'HTMLPurifier_AttrCollections_TestForConstruct',
+ array('performInclusions', 'expandIdentifiers')
+);
+
+class HTMLPurifier_AttrCollectionsTest extends HTMLPurifier_Harness
+{
+
+ function testConstruction() {
+
+ generate_mock_once('HTMLPurifier_AttrTypes');
+
+ $collections = new HTMLPurifier_AttrCollections_TestForConstruct();
+
+ $types = new HTMLPurifier_AttrTypesMock();
+
+ $modules = array();
+
+ $modules['Module1'] = new HTMLPurifier_HTMLModule();
+ $modules['Module1']->attr_collections = array(
+ 'Core' => array(
+ 0 => array('Soup', 'Undefined'),
+ 'attribute' => 'Type',
+ 'attribute-2' => 'Type2',
+ ),
+ 'Soup' => array(
+ 'attribute-3' => 'Type3-old' // overwritten
+ )
+ );
+
+ $modules['Module2'] = new HTMLPurifier_HTMLModule();
+ $modules['Module2']->attr_collections = array(
+ 'Core' => array(
+ 0 => array('Brocolli')
+ ),
+ 'Soup' => array(
+ 'attribute-3' => 'Type3'
+ ),
+ 'Brocolli' => array()
+ );
+
+ $collections->__construct($types, $modules);
+ // this is without identifier expansion or inclusions
+ $this->assertIdentical(
+ $collections->info,
+ array(
+ 'Core' => array(
+ 0 => array('Soup', 'Undefined', 'Brocolli'),
+ 'attribute' => 'Type',
+ 'attribute-2' => 'Type2'
+ ),
+ 'Soup' => array(
+ 'attribute-3' => 'Type3'
+ ),
+ 'Brocolli' => array()
+ )
+ );
+
+ }
+
+ function test_performInclusions() {
+
+ generate_mock_once('HTMLPurifier_AttrTypes');
+
+ $types = new HTMLPurifier_AttrTypesMock();
+ $collections = new HTMLPurifier_AttrCollections($types, array());
+ $collections->info = array(
+ 'Core' => array(0 => array('Inclusion', 'Undefined'), 'attr-original' => 'Type'),
+ 'Inclusion' => array(0 => array('SubInclusion'), 'attr' => 'Type'),
+ 'SubInclusion' => array('attr2' => 'Type')
+ );
+
+ $collections->performInclusions($collections->info['Core']);
+ $this->assertIdentical(
+ $collections->info['Core'],
+ array(
+ 'attr-original' => 'Type',
+ 'attr' => 'Type',
+ 'attr2' => 'Type'
+ )
+ );
+
+ // test recursive
+ $collections->info = array(
+ 'One' => array(0 => array('Two'), 'one' => 'Type'),
+ 'Two' => array(0 => array('One'), 'two' => 'Type')
+ );
+ $collections->performInclusions($collections->info['One']);
+ $this->assertIdentical(
+ $collections->info['One'],
+ array(
+ 'one' => 'Type',
+ 'two' => 'Type'
+ )
+ );
+
+ }
+
+ function test_expandIdentifiers() {
+
+ generate_mock_once('HTMLPurifier_AttrTypes');
+
+ $types = new HTMLPurifier_AttrTypesMock();
+ $collections = new HTMLPurifier_AttrCollections($types, array());
+
+ $attr = array(
+ 'attr1' => 'Color',
+ 'attr2*' => 'URI'
+ );
+ $c_object = new HTMLPurifier_AttrDef_HTML_Color();
+ $u_object = new HTMLPurifier_AttrDef_URI();
+
+ $types->setReturnValue('get', $c_object, array('Color'));
+ $types->setReturnValue('get', $u_object, array('URI'));
+
+ $collections->expandIdentifiers($attr, $types);
+
+ $u_object->required = true;
+ $this->assertIdentical(
+ $attr,
+ array(
+ 'attr1' => $c_object,
+ 'attr2' => $u_object
+ )
+ );
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/AlphaValueTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/AlphaValueTest.php
new file mode 100644
index 000000000..56efa306f
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/AlphaValueTest.php
@@ -0,0 +1,28 @@
+<?php
+
+class HTMLPurifier_AttrDef_CSS_AlphaValueTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+
+ $this->def = new HTMLPurifier_AttrDef_CSS_AlphaValue();
+
+ $this->assertDef('0');
+ $this->assertDef('1');
+ $this->assertDef('.2');
+
+ // clamping to [0.0, 1,0]
+ $this->assertDef('1.2', '1');
+ $this->assertDef('-3', '0');
+
+ $this->assertDef('0.0', '0');
+ $this->assertDef('1.0', '1');
+ $this->assertDef('000', '0');
+
+ $this->assertDef('asdf', false);
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BackgroundPositionTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BackgroundPositionTest.php
new file mode 100644
index 000000000..a216b2677
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BackgroundPositionTest.php
@@ -0,0 +1,68 @@
+<?php
+
+class HTMLPurifier_AttrDef_CSS_BackgroundPositionTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+
+ $this->def = new HTMLPurifier_AttrDef_CSS_BackgroundPosition();
+
+ // explicitly cited in spec
+ $this->assertDef('0% 0%');
+ $this->assertDef('100% 100%');
+ $this->assertDef('14% 84%');
+ $this->assertDef('2cm 1cm');
+ $this->assertDef('top');
+ $this->assertDef('left');
+ $this->assertDef('center');
+ $this->assertDef('right');
+ $this->assertDef('bottom');
+ $this->assertDef('left top');
+ $this->assertDef('center top');
+ $this->assertDef('right top');
+ $this->assertDef('left center');
+ $this->assertDef('right center');
+ $this->assertDef('left bottom');
+ $this->assertDef('center bottom');
+ $this->assertDef('right bottom');
+
+ // reordered due to internal impl details
+ $this->assertDef('top left', 'left top');
+ $this->assertDef('top center', 'top');
+ $this->assertDef('top right', 'right top');
+ $this->assertDef('center left', 'left');
+ $this->assertDef('center center', 'center');
+ $this->assertDef('center right', 'right');
+ $this->assertDef('bottom left', 'left bottom');
+ $this->assertDef('bottom center', 'bottom');
+ $this->assertDef('bottom right', 'right bottom');
+
+ // more cases from the defined syntax
+ $this->assertDef('1.32in 4ex');
+ $this->assertDef('-14% -84.65%');
+ $this->assertDef('-1in -4ex');
+ $this->assertDef('-1pc 2.3%');
+
+ // keyword mixing
+ $this->assertDef('3em top');
+ $this->assertDef('left 50%');
+
+ // fixable keyword mixing
+ $this->assertDef('top 3em', '3em top');
+ $this->assertDef('50% left', 'left 50%');
+
+ // whitespace collapsing
+ $this->assertDef('3em top', '3em top');
+ $this->assertDef("left\n \t foo ", 'left');
+
+ // invalid uses (we're going to be strict on these)
+ $this->assertDef('foo bar', false);
+ $this->assertDef('left left', 'left');
+ $this->assertDef('left right top bottom center left', 'left bottom');
+ $this->assertDef('0fr 9%', '9%');
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BackgroundTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BackgroundTest.php
new file mode 100644
index 000000000..83461c365
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BackgroundTest.php
@@ -0,0 +1,23 @@
+<?php
+
+class HTMLPurifier_AttrDef_CSS_BackgroundTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+
+ $config = HTMLPurifier_Config::createDefault();
+ $this->def = new HTMLPurifier_AttrDef_CSS_Background($config);
+
+ $valid = '#333 url("chess.png") repeat fixed 50% top';
+ $this->assertDef($valid);
+ $this->assertDef('url(\'chess.png\') #333 50% top repeat fixed', $valid);
+ $this->assertDef(
+ 'rgb(34, 56, 33) url(chess.png) repeat fixed top',
+ 'rgb(34,56,33) url("chess.png") repeat fixed top'
+ );
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BorderTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BorderTest.php
new file mode 100644
index 000000000..6cd77fd7a
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BorderTest.php
@@ -0,0 +1,21 @@
+<?php
+
+class HTMLPurifier_AttrDef_CSS_BorderTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+
+ $config = HTMLPurifier_Config::createDefault();
+ $this->def = new HTMLPurifier_AttrDef_CSS_Border($config);
+
+ $this->assertDef('thick solid red', 'thick solid #FF0000');
+ $this->assertDef('thick solid');
+ $this->assertDef('solid red', 'solid #FF0000');
+ $this->assertDef('1px solid #000');
+ $this->assertDef('1px solid rgb(0, 0, 0)', '1px solid rgb(0,0,0)');
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ColorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ColorTest.php
new file mode 100644
index 000000000..f3a74e897
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ColorTest.php
@@ -0,0 +1,41 @@
+<?php
+
+class HTMLPurifier_AttrDef_CSS_ColorTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+
+ $this->def = new HTMLPurifier_AttrDef_CSS_Color();
+
+ $this->assertDef('#F00');
+ $this->assertDef('#fff');
+ $this->assertDef('#eeeeee');
+ $this->assertDef('#808080');
+ $this->assertDef('rgb(255, 0, 0)', 'rgb(255,0,0)'); // rm spaces
+ $this->assertDef('rgb(100%,0%,0%)');
+ $this->assertDef('rgb(50.5%,23.2%,43.9%)'); // decimals okay
+
+ $this->assertDef('#G00', false);
+ $this->assertDef('cmyk(40, 23, 43, 23)', false);
+ $this->assertDef('rgb(0%, 23, 68%)', false);
+
+ // clip numbers outside sRGB gamut
+ $this->assertDef('rgb(200%, -10%, 0%)', 'rgb(100%,0%,0%)');
+ $this->assertDef('rgb(256,-23,34)', 'rgb(255,0,34)');
+
+ // color keywords, of course
+ $this->assertDef('red', '#FF0000');
+
+ // malformed hex declaration
+ $this->assertDef('808080', '#808080');
+ $this->assertDef('000000', '#000000');
+ $this->assertDef('fed', '#fed');
+
+ // maybe hex transformations would be another nice feature
+ // at the very least transform rgb percent to rgb integer
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/CompositeTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/CompositeTest.php
new file mode 100644
index 000000000..44bef5551
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/CompositeTest.php
@@ -0,0 +1,81 @@
+<?php
+
+class HTMLPurifier_AttrDef_CSS_Composite_Testable extends
+ HTMLPurifier_AttrDef_CSS_Composite
+{
+
+ // we need to pass by ref to get the mocks in
+ function HTMLPurifier_AttrDef_CSS_Composite_Testable(&$defs) {
+ $this->defs =& $defs;
+ }
+
+}
+
+class HTMLPurifier_AttrDef_CSS_CompositeTest extends HTMLPurifier_AttrDefHarness
+{
+
+ protected $def1, $def2;
+
+ function test() {
+
+ generate_mock_once('HTMLPurifier_AttrDef');
+
+ $config = HTMLPurifier_Config::createDefault();
+ $context = new HTMLPurifier_Context();
+
+ // first test: value properly validates on first definition
+ // so second def is never called
+
+ $def1 = new HTMLPurifier_AttrDefMock();
+ $def2 = new HTMLPurifier_AttrDefMock();
+ $defs = array(&$def1, &$def2);
+ $def = new HTMLPurifier_AttrDef_CSS_Composite_Testable($defs);
+ $input = 'FOOBAR';
+ $output = 'foobar';
+ $def1_params = array($input, $config, $context);
+ $def1->expectOnce('validate', $def1_params);
+ $def1->setReturnValue('validate', $output, $def1_params);
+ $def2->expectNever('validate');
+
+ $result = $def->validate($input, $config, $context);
+ $this->assertIdentical($output, $result);
+
+ // second test, first def fails, second def works
+
+ $def1 = new HTMLPurifier_AttrDefMock();
+ $def2 = new HTMLPurifier_AttrDefMock();
+ $defs = array(&$def1, &$def2);
+ $def = new HTMLPurifier_AttrDef_CSS_Composite_Testable($defs);
+ $input = 'BOOMA';
+ $output = 'booma';
+ $def_params = array($input, $config, $context);
+ $def1->expectOnce('validate', $def_params);
+ $def1->setReturnValue('validate', false, $def_params);
+ $def2->expectOnce('validate', $def_params);
+ $def2->setReturnValue('validate', $output, $def_params);
+
+ $result = $def->validate($input, $config, $context);
+ $this->assertIdentical($output, $result);
+
+ // third test, all fail, so composite faiils
+
+ $def1 = new HTMLPurifier_AttrDefMock();
+ $def2 = new HTMLPurifier_AttrDefMock();
+ $defs = array(&$def1, &$def2);
+ $def = new HTMLPurifier_AttrDef_CSS_Composite_Testable($defs);
+ $input = 'BOOMA';
+ $output = false;
+ $def_params = array($input, $config, $context);
+ $def1->expectOnce('validate', $def_params);
+ $def1->setReturnValue('validate', false, $def_params);
+ $def2->expectOnce('validate', $def_params);
+ $def2->setReturnValue('validate', false, $def_params);
+
+ $result = $def->validate($input, $config, $context);
+ $this->assertIdentical($output, $result);
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FilterTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FilterTest.php
new file mode 100644
index 000000000..7795643f1
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FilterTest.php
@@ -0,0 +1,29 @@
+<?php
+
+class HTMLPurifier_AttrDef_CSS_FilterTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+
+ $this->def = new HTMLPurifier_AttrDef_CSS_Filter();
+
+ $this->assertDef('none');
+
+ $this->assertDef('alpha(opacity=0)');
+ $this->assertDef('alpha(opacity=100)');
+ $this->assertDef('alpha(opacity=50)');
+ $this->assertDef('alpha(opacity=342)', 'alpha(opacity=100)');
+ $this->assertDef('alpha(opacity=-23)', 'alpha(opacity=0)');
+
+ $this->assertDef('alpha ( opacity = 0 )', 'alpha(opacity=0)');
+ $this->assertDef('alpha(opacity=0,opacity=100)', 'alpha(opacity=0)');
+
+ $this->assertDef('progid:DXImageTransform.Microsoft.Alpha(opacity=20)');
+
+ $this->assertDef('progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)', false);
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FontFamilyTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FontFamilyTest.php
new file mode 100644
index 000000000..fda8e01ff
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FontFamilyTest.php
@@ -0,0 +1,52 @@
+<?php
+
+class HTMLPurifier_AttrDef_CSS_FontFamilyTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+
+ $this->def = new HTMLPurifier_AttrDef_CSS_FontFamily();
+
+ $this->assertDef('Gill, Helvetica, sans-serif');
+ $this->assertDef("'Times New Roman', serif");
+ $this->assertDef("\"Times New Roman\"", "'Times New Roman'");
+ $this->assertDef('01234');
+ $this->assertDef(',', false);
+ $this->assertDef('Times New Roman, serif', "'Times New Roman', serif");
+ $this->assertDef($d = "'\xE5\xAE\x8B\xE4\xBD\x93'");
+ $this->assertDef("\xE5\xAE\x8B\xE4\xBD\x93", $d);
+ $this->assertDef("'\\01'", "''");
+ $this->assertDef("'\\20'", "' '");
+ $this->assertDef("\\0020", "' '");
+ $this->assertDef("'\\000045'", "E");
+ $this->assertDef("','", false);
+ $this->assertDef("',' foobar','", "' foobar'");
+ $this->assertDef("'\\000045a'", "Ea");
+ $this->assertDef("'\\00045 a'", "Ea");
+ $this->assertDef("'\\00045 a'", "'E a'");
+ $this->assertDef("'\\\nf'", "f");
+ // No longer supported, except maybe in NoJS mode (see source
+ // file for more explanation)
+ //$this->assertDef($d = '"John\'s Font"');
+ //$this->assertDef("John's Font", $d);
+ //$this->assertDef("'\\','f'", "\"\\5C \", f");
+ //$this->assertDef("'\\27'", "\"'\"");
+ //$this->assertDef('"\\22"', "\"\\22 \"");
+ //$this->assertDef('"\\""', "\"\\22 \"");
+ //$this->assertDef('"\'"', "\"'\"");
+ }
+
+ function testAllowed() {
+ $this->config->set('CSS.AllowedFonts', array('serif', 'Times New Roman'));
+
+ $this->assertDef('serif');
+ $this->assertDef('sans-serif', false);
+ $this->assertDef('serif, sans-serif', 'serif');
+ $this->assertDef('Times New Roman', "'Times New Roman'");
+ $this->assertDef("'Times New Roman'");
+ $this->assertDef('foo', false);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FontTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FontTest.php
new file mode 100644
index 000000000..91870d13e
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FontTest.php
@@ -0,0 +1,34 @@
+<?php
+
+class HTMLPurifier_AttrDef_CSS_FontTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+
+ $config = HTMLPurifier_Config::createDefault();
+ $this->def = new HTMLPurifier_AttrDef_CSS_Font($config);
+
+ // hodgepodge of usage cases from W3C spec, but " -> '
+ $this->assertDef('12px/14px sans-serif');
+ $this->assertDef('80% sans-serif');
+ $this->assertDef("x-large/110% 'New Century Schoolbook', serif");
+ $this->assertDef('bold italic large Palatino, serif');
+ $this->assertDef('normal small-caps 120%/120% fantasy');
+ $this->assertDef("300 italic 1.3em/1.7em 'FB Armada', sans-serif");
+ $this->assertDef('600 9px Charcoal');
+ $this->assertDef('600 9px/ 12px Charcoal', '600 9px/12px Charcoal');
+
+ // spacing
+ $this->assertDef('12px / 14px sans-serif', '12px/14px sans-serif');
+
+ // system fonts
+ $this->assertDef('menu');
+
+ $this->assertDef('800', false);
+ $this->assertDef('600 9px//12px Charcoal', false);
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ImportantDecoratorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ImportantDecoratorTest.php
new file mode 100644
index 000000000..c7fa8a0fa
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ImportantDecoratorTest.php
@@ -0,0 +1,49 @@
+<?php
+
+class HTMLPurifier_AttrDef_CSS_ImportantDecoratorTest extends HTMLPurifier_AttrDefHarness
+{
+
+ /** Mock AttrDef decorator is wrapping */
+ protected $mock;
+
+ function setUp() {
+ generate_mock_once('HTMLPurifier_AttrDef');
+ $this->mock = new HTMLPurifier_AttrDefMock();
+ $this->def = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($this->mock, true);
+ }
+
+ protected function setMock($input, $output = null) {
+ if ($output === null) $output = $input;
+ $this->mock->expectOnce('validate', array($input, $this->config, $this->context));
+ $this->mock->setReturnValue('validate', $output);
+ }
+
+ function testImportant() {
+ $this->setMock('23');
+ $this->assertDef('23 !important');
+ }
+
+ function testImportantInternalDefChanged() {
+ $this->setMock('23', '24');
+ $this->assertDef('23 !important', '24 !important');
+ }
+
+ function testImportantWithSpace() {
+ $this->setMock('23');
+ $this->assertDef('23 ! important ', '23 !important');
+ }
+
+ function testFakeImportant() {
+ $this->setMock('! foo important');
+ $this->assertDef('! foo important');
+ }
+
+ function testStrip() {
+ $this->def = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($this->mock, false);
+ $this->setMock('23');
+ $this->assertDef('23 ! important ', '23');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/LengthTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/LengthTest.php
new file mode 100644
index 000000000..9d9fc41f2
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/LengthTest.php
@@ -0,0 +1,48 @@
+<?php
+
+class HTMLPurifier_AttrDef_CSS_LengthTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+
+ $this->def = new HTMLPurifier_AttrDef_CSS_Length();
+
+ $this->assertDef('0');
+ $this->assertDef('0px');
+ $this->assertDef('4.5px');
+ $this->assertDef('-4.5px');
+ $this->assertDef('3ex');
+ $this->assertDef('3em');
+ $this->assertDef('3in');
+ $this->assertDef('3cm');
+ $this->assertDef('3mm');
+ $this->assertDef('3pt');
+ $this->assertDef('3pc');
+
+ $this->assertDef('3PX', '3px');
+
+ $this->assertDef('3', false);
+ $this->assertDef('3miles', false);
+
+ }
+
+ function testNonNegative() {
+
+ $this->def = new HTMLPurifier_AttrDef_CSS_Length('0');
+
+ $this->assertDef('3cm');
+ $this->assertDef('-3mm', false);
+
+ }
+
+ function testBounding() {
+ $this->def = new HTMLPurifier_AttrDef_CSS_Length('-1in', '1in');
+ $this->assertDef('1cm');
+ $this->assertDef('-1cm');
+ $this->assertDef('0');
+ $this->assertDef('1em', false);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ListStyleTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ListStyleTest.php
new file mode 100644
index 000000000..070066705
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ListStyleTest.php
@@ -0,0 +1,35 @@
+<?php
+
+class HTMLPurifier_AttrDef_CSS_ListStyleTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+
+ $config = HTMLPurifier_Config::createDefault();
+ $this->def = new HTMLPurifier_AttrDef_CSS_ListStyle($config);
+
+ $this->assertDef('lower-alpha');
+ $this->assertDef('upper-roman inside');
+ $this->assertDef('circle outside');
+ $this->assertDef('inside');
+ $this->assertDef('none');
+ $this->assertDef('url("foo.gif")');
+ $this->assertDef('circle url("foo.gif") inside');
+
+ // invalid values
+ $this->assertDef('outside inside', 'outside');
+
+ // ordering
+ $this->assertDef('url(foo.gif) none', 'none url("foo.gif")');
+ $this->assertDef('circle lower-alpha', 'circle');
+ // the spec is ambiguous about what happens in these
+ // cases, so we're going off the W3C CSS validator
+ $this->assertDef('disc none', 'disc');
+ $this->assertDef('none disc', 'none');
+
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/MultipleTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/MultipleTest.php
new file mode 100644
index 000000000..4461cb508
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/MultipleTest.php
@@ -0,0 +1,28 @@
+<?php
+
+// borrowed for the sakes of this test
+class HTMLPurifier_AttrDef_CSS_MultipleTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+ $this->def = new HTMLPurifier_AttrDef_CSS_Multiple(
+ new HTMLPurifier_AttrDef_Integer()
+ );
+
+ $this->assertDef('1 2 3 4');
+ $this->assertDef('6');
+ $this->assertDef('4 5');
+ $this->assertDef(' 2 54 2 3', '2 54 2 3');
+ $this->assertDef("6\r3", '6 3');
+
+ $this->assertDef('asdf', false);
+ $this->assertDef('a s d f', false);
+ $this->assertDef('1 2 3 4 5', '1 2 3 4');
+ $this->assertDef('1 2 invalid 3', '1 2 3');
+
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/NumberTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/NumberTest.php
new file mode 100644
index 000000000..94e6ea8cf
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/NumberTest.php
@@ -0,0 +1,51 @@
+<?php
+
+class HTMLPurifier_AttrDef_CSS_NumberTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+
+ $this->def = new HTMLPurifier_AttrDef_CSS_Number();
+
+ $this->assertDef('0');
+ $this->assertDef('0.0', '0');
+ $this->assertDef('1.0', '1');
+ $this->assertDef('34');
+ $this->assertDef('4.5');
+ $this->assertDef('.5');
+ $this->assertDef('0.5', '.5');
+ $this->assertDef('-56.9');
+
+ $this->assertDef('0.', '0');
+ $this->assertDef('.0', '0');
+ $this->assertDef('0.0', '0');
+
+ $this->assertDef('1.', '1');
+ $this->assertDef('.1', '.1');
+
+ $this->assertDef('1.0', '1');
+ $this->assertDef('0.1', '.1');
+
+ $this->assertDef('000', '0');
+ $this->assertDef(' 9', '9');
+ $this->assertDef('+5.0000', '5');
+ $this->assertDef('02.20', '2.2');
+ $this->assertDef('2.', '2');
+
+ $this->assertDef('.', false);
+ $this->assertDef('asdf', false);
+ $this->assertDef('0.5.6', false);
+
+ }
+
+ function testNonNegative() {
+
+ $this->def = new HTMLPurifier_AttrDef_CSS_Number(true);
+ $this->assertDef('23');
+ $this->assertDef('-12', false);
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/PercentageTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/PercentageTest.php
new file mode 100644
index 000000000..f712af1d2
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/PercentageTest.php
@@ -0,0 +1,24 @@
+<?php
+
+class HTMLPurifier_AttrDef_CSS_PercentageTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+
+ $this->def = new HTMLPurifier_AttrDef_CSS_Percentage();
+
+ $this->assertDef('10%');
+ $this->assertDef('1.607%');
+ $this->assertDef('-567%');
+
+ $this->assertDef(' 100% ', '100%');
+
+ $this->assertDef('5', false);
+ $this->assertDef('asdf', false);
+ $this->assertDef('%', false);
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/TextDecorationTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/TextDecorationTest.php
new file mode 100644
index 000000000..dd714d206
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/TextDecorationTest.php
@@ -0,0 +1,27 @@
+<?php
+
+class HTMLPurifier_AttrDef_CSS_TextDecorationTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function testCaseInsensitive() {
+
+ $this->def = new HTMLPurifier_AttrDef_CSS_TextDecoration();
+
+ $this->assertDef('none');
+ $this->assertDef('none underline', 'underline');
+
+ $this->assertDef('underline');
+ $this->assertDef('overline');
+ $this->assertDef('line-through overline underline');
+ $this->assertDef('overline line-through');
+ $this->assertDef('UNDERLINE', 'underline');
+ $this->assertDef(' underline line-through ', 'underline line-through');
+
+ $this->assertDef('foobar underline', 'underline');
+ $this->assertDef('blink', false);
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/URITest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/URITest.php
new file mode 100644
index 000000000..3d6f5791e
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/URITest.php
@@ -0,0 +1,29 @@
+<?php
+
+class HTMLPurifier_AttrDef_CSS_URITest extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+
+ $this->def = new HTMLPurifier_AttrDef_CSS_URI();
+
+ $this->assertDef('', false);
+
+ // we could be nice but we won't be
+ $this->assertDef('http://www.example.com/', false);
+
+ $this->assertDef('url(', false);
+ $this->assertDef('url("")', true);
+ $result = 'url("http://www.example.com/")';
+ $this->assertDef('url(http://www.example.com/)', $result);
+ $this->assertDef('url("http://www.example.com/")', $result);
+ $this->assertDef("url('http://www.example.com/')", $result);
+ $this->assertDef(
+ ' url( "http://www.example.com/" ) ', $result);
+ $this->assertDef("url(http://www.example.com/foo,bar\)\'\()",
+ 'url("http://www.example.com/foo,bar%29%27%28")');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSSTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSSTest.php
new file mode 100644
index 000000000..56917aece
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSSTest.php
@@ -0,0 +1,164 @@
+<?php
+
+class HTMLPurifier_AttrDef_CSSTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function setup() {
+ parent::setup();
+ $this->def = new HTMLPurifier_AttrDef_CSS();
+ }
+
+ function test() {
+
+ // regular cases, singular
+ $this->assertDef('text-align:right;');
+ $this->assertDef('border-left-style:solid;');
+ $this->assertDef('border-style:solid dotted;');
+ $this->assertDef('clear:right;');
+ $this->assertDef('float:left;');
+ $this->assertDef('font-style:italic;');
+ $this->assertDef('font-variant:small-caps;');
+ $this->assertDef('font-weight:bold;');
+ $this->assertDef('list-style-position:outside;');
+ $this->assertDef('list-style-type:upper-roman;');
+ $this->assertDef('list-style:upper-roman inside;');
+ $this->assertDef('text-transform:capitalize;');
+ $this->assertDef('background-color:rgb(0,0,255);');
+ $this->assertDef('background-color:transparent;');
+ $this->assertDef('background:#333 url("chess.png") repeat fixed 50% top;');
+ $this->assertDef('color:#F00;');
+ $this->assertDef('border-top-color:#F00;');
+ $this->assertDef('border-color:#F00 #FF0;');
+ $this->assertDef('border-top-width:thin;');
+ $this->assertDef('border-top-width:12px;');
+ $this->assertDef('border-width:5px 1px 4px 2px;');
+ $this->assertDef('border-top-width:-12px;', false);
+ $this->assertDef('letter-spacing:normal;');
+ $this->assertDef('letter-spacing:2px;');
+ $this->assertDef('word-spacing:normal;');
+ $this->assertDef('word-spacing:3em;');
+ $this->assertDef('font-size:200%;');
+ $this->assertDef('font-size:larger;');
+ $this->assertDef('font-size:12pt;');
+ $this->assertDef('line-height:2;');
+ $this->assertDef('line-height:2em;');
+ $this->assertDef('line-height:20%;');
+ $this->assertDef('line-height:normal;');
+ $this->assertDef('line-height:-20%;', false);
+ $this->assertDef('margin-left:5px;');
+ $this->assertDef('margin-right:20%;');
+ $this->assertDef('margin-top:auto;');
+ $this->assertDef('margin:auto 5%;');
+ $this->assertDef('padding-bottom:5px;');
+ $this->assertDef('padding-top:20%;');
+ $this->assertDef('padding:20% 10%;');
+ $this->assertDef('padding-top:-20%;', false);
+ $this->assertDef('text-indent:3em;');
+ $this->assertDef('text-indent:5%;');
+ $this->assertDef('text-indent:-3em;');
+ $this->assertDef('width:50%;');
+ $this->assertDef('width:50px;');
+ $this->assertDef('width:auto;');
+ $this->assertDef('width:-50px;', false);
+ $this->assertDef('text-decoration:underline;');
+ $this->assertDef('font-family:sans-serif;');
+ $this->assertDef("font-family:Gill, 'Times New Roman', sans-serif;");
+ $this->assertDef('font:12px serif;');
+ $this->assertDef('border:1px solid #000;');
+ $this->assertDef('border-bottom:2em double #FF00FA;');
+ $this->assertDef('border-collapse:collapse;');
+ $this->assertDef('border-collapse:separate;');
+ $this->assertDef('caption-side:top;');
+ $this->assertDef('vertical-align:middle;');
+ $this->assertDef('vertical-align:12px;');
+ $this->assertDef('vertical-align:50%;');
+ $this->assertDef('table-layout:fixed;');
+ $this->assertDef('list-style-image:url("nice.jpg");');
+ $this->assertDef('list-style:disc url("nice.jpg") inside;');
+ $this->assertDef('background-image:url("foo.jpg");');
+ $this->assertDef('background-image:none;');
+ $this->assertDef('background-repeat:repeat-y;');
+ $this->assertDef('background-attachment:fixed;');
+ $this->assertDef('background-position:left 90%;');
+ $this->assertDef('border-spacing:1em;');
+ $this->assertDef('border-spacing:1em 2em;');
+
+ // duplicates
+ $this->assertDef('text-align:right;text-align:left;',
+ 'text-align:left;');
+
+ // a few composites
+ $this->assertDef('font-variant:small-caps;font-weight:900;');
+ $this->assertDef('float:right;text-align:right;');
+
+ // selective removal
+ $this->assertDef('text-transform:capitalize;destroy:it;',
+ 'text-transform:capitalize;');
+
+ // inherit works for everything
+ $this->assertDef('text-align:inherit;');
+
+ // bad props
+ $this->assertDef('nodice:foobar;', false);
+ $this->assertDef('position:absolute;', false);
+ $this->assertDef('background-image:url(\'javascript:alert\(\)\');', false);
+
+ // airy input
+ $this->assertDef(' font-weight : bold; color : #ff0000',
+ 'font-weight:bold;color:#ff0000;');
+
+ // case-insensitivity
+ $this->assertDef('FLOAT:LEFT;', 'float:left;');
+
+ // !important stripping
+ $this->assertDef('float:left !important;', 'float:left;');
+
+ }
+
+ function testProprietary() {
+ $this->config->set('CSS.Proprietary', true);
+
+ $this->assertDef('scrollbar-arrow-color:#ff0;');
+ $this->assertDef('scrollbar-base-color:#ff6347;');
+ $this->assertDef('scrollbar-darkshadow-color:#ffa500;');
+ $this->assertDef('scrollbar-face-color:#008080;');
+ $this->assertDef('scrollbar-highlight-color:#ff69b4;');
+ $this->assertDef('scrollbar-shadow-color:#f0f;');
+
+ $this->assertDef('opacity:.2;');
+ $this->assertDef('-moz-opacity:.2;');
+ $this->assertDef('-khtml-opacity:.2;');
+ $this->assertDef('filter:alpha(opacity=20);');
+
+ }
+
+ function testImportant() {
+ $this->config->set('CSS.AllowImportant', true);
+ $this->assertDef('float:left !important;');
+ }
+
+ function testTricky() {
+ $this->config->set('CSS.AllowTricky', true);
+ $this->assertDef('display:none;');
+ $this->assertDef('visibility:visible;');
+ $this->assertDef('overflow:scroll;');
+ }
+
+ function testForbidden() {
+ $this->config->set('CSS.ForbiddenProperties', 'float');
+ $this->assertDef('float:left;', false);
+ $this->assertDef('text-align:right;');
+ }
+
+ function testTrusted() {
+ $this->config->set('CSS.Trusted', true);
+ $this->assertDef('position:relative;');
+ $this->assertDef('left:2px;');
+ $this->assertDef('right:100%;');
+ $this->assertDef('top:auto;');
+ $this->assertDef('z-index:-2;');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/EnumTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/EnumTest.php
new file mode 100644
index 000000000..7722c1bc2
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/EnumTest.php
@@ -0,0 +1,37 @@
+<?php
+
+class HTMLPurifier_AttrDef_EnumTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function testCaseInsensitive() {
+ $this->def = new HTMLPurifier_AttrDef_Enum(array('one', 'two'));
+ $this->assertDef('one');
+ $this->assertDef('ONE', 'one');
+ }
+
+ function testCaseSensitive() {
+ $this->def = new HTMLPurifier_AttrDef_Enum(array('one', 'two'), true);
+ $this->assertDef('one');
+ $this->assertDef('ONE', false);
+ }
+
+ function testFixing() {
+ $this->def = new HTMLPurifier_AttrDef_Enum(array('one'));
+ $this->assertDef(' one ', 'one');
+ }
+
+ function test_make() {
+ $factory = new HTMLPurifier_AttrDef_Enum();
+
+ $def = $factory->make('foo,bar');
+ $def2 = new HTMLPurifier_AttrDef_Enum(array('foo', 'bar'));
+ $this->assertIdentical($def, $def2);
+
+ $def = $factory->make('s:foo,BAR');
+ $def2 = new HTMLPurifier_AttrDef_Enum(array('foo', 'BAR'), true);
+ $this->assertIdentical($def, $def2);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/BoolTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/BoolTest.php
new file mode 100644
index 000000000..060d0fc8f
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/BoolTest.php
@@ -0,0 +1,22 @@
+<?php
+
+class HTMLPurifier_AttrDef_HTML_BoolTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+ $this->def = new HTMLPurifier_AttrDef_HTML_Bool('foo');
+ $this->assertDef('foo');
+ $this->assertDef('', false);
+ $this->assertDef('bar', 'foo');
+ }
+
+ function test_make() {
+ $factory = new HTMLPurifier_AttrDef_HTML_Bool();
+ $def = $factory->make('foo');
+ $def2 = new HTMLPurifier_AttrDef_HTML_Bool('foo');
+ $this->assertIdentical($def, $def2);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/ClassTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/ClassTest.php
new file mode 100644
index 000000000..6effd3cde
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/ClassTest.php
@@ -0,0 +1,48 @@
+<?php
+
+class HTMLPurifier_AttrDef_HTML_ClassTest extends HTMLPurifier_AttrDef_HTML_NmtokensTest
+{
+ function setUp() {
+ parent::setUp();
+ $this->def = new HTMLPurifier_AttrDef_HTML_Class();
+ }
+ function testAllowedClasses() {
+ $this->config->set('Attr.AllowedClasses', array('foo'));
+ $this->assertDef('foo');
+ $this->assertDef('bar', false);
+ $this->assertDef('foo bar', 'foo');
+ }
+ function testForbiddenClasses() {
+ $this->config->set('Attr.ForbiddenClasses', array('bar'));
+ $this->assertDef('foo');
+ $this->assertDef('bar', false);
+ $this->assertDef('foo bar', 'foo');
+ }
+ function testDefault() {
+ $this->assertDef('valid');
+ $this->assertDef('a0-_');
+ $this->assertDef('-valid');
+ $this->assertDef('_valid');
+ $this->assertDef('double valid');
+
+ $this->assertDef('0stillvalid');
+ $this->assertDef('-0');
+
+ // test conditional replacement
+ $this->assertDef('validassoc 0valid', 'validassoc 0valid');
+
+ // test whitespace leniency
+ $this->assertDef(" double\nvalid\r", 'double valid');
+
+ // test case sensitivity
+ $this->assertDef('VALID');
+
+ // test duplicate removal
+ $this->assertDef('valid valid', 'valid');
+ }
+ function testXHTML11Behavior() {
+ $this->config->set('HTML.Doctype', 'XHTML 1.1');
+ $this->assertDef('0invalid', false);
+ $this->assertDef('valid valid', 'valid');
+ }
+}
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/ColorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/ColorTest.php
new file mode 100644
index 000000000..8b4a46347
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/ColorTest.php
@@ -0,0 +1,20 @@
+<?php
+
+class HTMLPurifier_AttrDef_HTML_ColorTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+ $this->def = new HTMLPurifier_AttrDef_HTML_Color();
+ $this->assertDef('', false);
+ $this->assertDef('foo', false);
+ $this->assertDef('43', false);
+ $this->assertDef('red', '#FF0000');
+ $this->assertDef('#FF0000');
+ $this->assertDef('#453443');
+ $this->assertDef('453443', '#453443');
+ $this->assertDef('#345', '#334455');
+ $this->assertDef('120', '#112200');
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/FrameTargetTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/FrameTargetTest.php
new file mode 100644
index 000000000..7d3e24c75
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/FrameTargetTest.php
@@ -0,0 +1,28 @@
+<?php
+
+class HTMLPurifier_AttrDef_HTML_FrameTargetTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function setup() {
+ parent::setup();
+ $this->def = new HTMLPurifier_AttrDef_HTML_FrameTarget();
+ }
+
+ function testNoneAllowed() {
+ $this->assertDef('', false);
+ $this->assertDef('foo', false);
+ $this->assertDef('_blank', false);
+ $this->assertDef('baz', false);
+ }
+
+ function test() {
+ $this->config->set('Attr.AllowedFrameTargets', 'foo,_blank');
+ $this->assertDef('', false);
+ $this->assertDef('foo');
+ $this->assertDef('_blank');
+ $this->assertDef('baz', false);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/IDTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/IDTest.php
new file mode 100644
index 000000000..245db16da
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/IDTest.php
@@ -0,0 +1,108 @@
+<?php
+
+class HTMLPurifier_AttrDef_HTML_IDTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function setUp() {
+ parent::setUp();
+
+ $id_accumulator = new HTMLPurifier_IDAccumulator();
+ $this->context->register('IDAccumulator', $id_accumulator);
+ $this->config->set('Attr.EnableID', true);
+ $this->def = new HTMLPurifier_AttrDef_HTML_ID();
+
+ }
+
+ function test() {
+
+ // valid ID names
+ $this->assertDef('alpha');
+ $this->assertDef('al_ha');
+ $this->assertDef('a0-:.');
+ $this->assertDef('a');
+
+ // invalid ID names
+ $this->assertDef('<asa', false);
+ $this->assertDef('0123', false);
+ $this->assertDef('.asa', false);
+
+ // test duplicate detection
+ $this->assertDef('once');
+ $this->assertDef('once', false);
+
+ // valid once whitespace stripped, but needs to be amended
+ $this->assertDef(' whee ', 'whee');
+
+ }
+
+ function testPrefix() {
+
+ $this->config->set('Attr.IDPrefix', 'user_');
+
+ $this->assertDef('alpha', 'user_alpha');
+ $this->assertDef('<asa', false);
+ $this->assertDef('once', 'user_once');
+ $this->assertDef('once', false);
+
+ // if already prefixed, leave alone
+ $this->assertDef('user_alas');
+ $this->assertDef('user_user_alas'); // how to bypass
+
+ }
+
+ function testTwoPrefixes() {
+
+ $this->config->set('Attr.IDPrefix', 'user_');
+ $this->config->set('Attr.IDPrefixLocal', 'story95_');
+
+ $this->assertDef('alpha', 'user_story95_alpha');
+ $this->assertDef('<asa', false);
+ $this->assertDef('once', 'user_story95_once');
+ $this->assertDef('once', false);
+
+ $this->assertDef('user_story95_alas');
+ $this->assertDef('user_alas', 'user_story95_user_alas'); // !
+ }
+
+ function testLocalPrefixWithoutMainPrefix() {
+ // no effect when IDPrefix isn't set
+ $this->config->set('Attr.IDPrefix', '');
+ $this->config->set('Attr.IDPrefixLocal', 'story95_');
+ $this->expectError('%Attr.IDPrefixLocal cannot be used unless '.
+ '%Attr.IDPrefix is set');
+ $this->assertDef('amherst');
+
+ }
+
+ // reference functionality is disabled for now
+ function disabled_testIDReference() {
+
+ $this->def = new HTMLPurifier_AttrDef_HTML_ID(true);
+
+ $this->assertDef('good_id');
+ $this->assertDef('good_id'); // duplicates okay
+ $this->assertDef('<b>', false);
+
+ $this->def = new HTMLPurifier_AttrDef_HTML_ID();
+
+ $this->assertDef('good_id');
+ $this->assertDef('good_id', false); // duplicate now not okay
+
+ $this->def = new HTMLPurifier_AttrDef_HTML_ID(true);
+
+ $this->assertDef('good_id'); // reference still okay
+
+ }
+
+ function testRegexp() {
+
+ $this->config->set('Attr.IDBlacklistRegexp', '/^g_/');
+
+ $this->assertDef('good_id');
+ $this->assertDef('g_bad_id', false);
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/LengthTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/LengthTest.php
new file mode 100644
index 000000000..d165e30b5
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/LengthTest.php
@@ -0,0 +1,32 @@
+<?php
+
+class HTMLPurifier_AttrDef_HTML_LengthTest extends HTMLPurifier_AttrDef_HTML_PixelsTest
+{
+
+ function setup() {
+ $this->def = new HTMLPurifier_AttrDef_HTML_Length();
+ }
+
+ function test() {
+
+ // pixel check
+ parent::test();
+
+ // percent check
+ $this->assertDef('25%');
+
+ // Firefox maintains percent, so will we
+ $this->assertDef('0%');
+
+ // 0% <= percent <= 100%
+ $this->assertDef('-15%', '0%');
+ $this->assertDef('120%', '100%');
+
+ // fractional percents, apparently, aren't allowed
+ $this->assertDef('56.5%', '56%');
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/LinkTypesTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/LinkTypesTest.php
new file mode 100644
index 000000000..d90b65b1f
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/LinkTypesTest.php
@@ -0,0 +1,21 @@
+<?php
+
+class HTMLPurifier_AttrDef_HTML_LinkTypesTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function testNull() {
+
+ $this->def = new HTMLPurifier_AttrDef_HTML_LinkTypes('rel');
+ $this->config->set('Attr.AllowedRel', array('nofollow', 'foo'));
+
+ $this->assertDef('', false);
+ $this->assertDef('nofollow', true);
+ $this->assertDef('nofollow foo', true);
+ $this->assertDef('nofollow bar', 'nofollow');
+ $this->assertDef('bar', false);
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/MultiLengthTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/MultiLengthTest.php
new file mode 100644
index 000000000..eb6f34011
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/MultiLengthTest.php
@@ -0,0 +1,28 @@
+<?php
+
+class HTMLPurifier_AttrDef_HTML_MultiLengthTest extends HTMLPurifier_AttrDef_HTML_LengthTest
+{
+
+ function setup() {
+ $this->def = new HTMLPurifier_AttrDef_HTML_MultiLength();
+ }
+
+ function test() {
+
+ // length check
+ parent::test();
+
+ $this->assertDef('*');
+ $this->assertDef('1*', '*');
+ $this->assertDef('56*');
+
+ $this->assertDef('**', false); // plain old bad
+
+ $this->assertDef('5.4*', '5*'); // no decimals
+ $this->assertDef('-3*', false); // no negatives
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/NmtokensTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/NmtokensTest.php
new file mode 100644
index 000000000..bb64ff6e2
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/NmtokensTest.php
@@ -0,0 +1,35 @@
+<?php
+
+class HTMLPurifier_AttrDef_HTML_NmtokensTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->def = new HTMLPurifier_AttrDef_HTML_Nmtokens();
+ }
+
+ function testDefault() {
+
+ $this->assertDef('valid');
+ $this->assertDef('a0-_');
+ $this->assertDef('-valid');
+ $this->assertDef('_valid');
+ $this->assertDef('double valid');
+
+ $this->assertDef('0invalid', false);
+ $this->assertDef('-0', false);
+
+ // test conditional replacement
+ $this->assertDef('validassoc 0invalid', 'validassoc');
+
+ // test whitespace leniency
+ $this->assertDef(" double\nvalid\r", 'double valid');
+
+ // test case sensitivity
+ $this->assertDef('VALID');
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/PixelsTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/PixelsTest.php
new file mode 100644
index 000000000..08f25be64
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/PixelsTest.php
@@ -0,0 +1,45 @@
+<?php
+
+class HTMLPurifier_AttrDef_HTML_PixelsTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function setup() {
+ $this->def = new HTMLPurifier_AttrDef_HTML_Pixels();
+ }
+
+ function test() {
+
+ $this->assertDef('1');
+ $this->assertDef('0');
+
+ $this->assertDef('2px', '2'); // rm px suffix
+
+ $this->assertDef('dfs', false); // totally invalid value
+
+ // conceivably we could repair this value, but we won't for now
+ $this->assertDef('9in', false);
+
+ // test trim
+ $this->assertDef(' 45 ', '45');
+
+ // no negatives
+ $this->assertDef('-2', '0');
+
+ // remove empty
+ $this->assertDef('', false);
+
+ // round down
+ $this->assertDef('4.9', '4');
+
+ }
+
+ function test_make() {
+ $factory = new HTMLPurifier_AttrDef_HTML_Pixels();
+ $this->def = $factory->make('30');
+ $this->assertDef('25');
+ $this->assertDef('35', '30');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/IntegerTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/IntegerTest.php
new file mode 100644
index 000000000..a941e31ab
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/IntegerTest.php
@@ -0,0 +1,61 @@
+<?php
+
+class HTMLPurifier_AttrDef_IntegerTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+
+ $this->def = new HTMLPurifier_AttrDef_Integer();
+
+ $this->assertDef('0');
+ $this->assertDef('1');
+ $this->assertDef('-1');
+ $this->assertDef('-10');
+ $this->assertDef('14');
+ $this->assertDef('+24', '24');
+ $this->assertDef(' 14 ', '14');
+ $this->assertDef('-0', '0');
+
+ $this->assertDef('-1.4', false);
+ $this->assertDef('3.4', false);
+ $this->assertDef('asdf', false); // must not return zero
+ $this->assertDef('2in', false); // must not return zero
+
+ }
+
+ function assertRange($negative, $zero, $positive) {
+ $this->assertDef('-100', $negative);
+ $this->assertDef('-1', $negative);
+ $this->assertDef('0', $zero);
+ $this->assertDef('1', $positive);
+ $this->assertDef('42', $positive);
+ }
+
+ function testRange() {
+
+ $this->def = new HTMLPurifier_AttrDef_Integer(false);
+ $this->assertRange(false, true, true); // non-negative
+
+ $this->def = new HTMLPurifier_AttrDef_Integer(false, false);
+ $this->assertRange(false, false, true); // positive
+
+
+ // fringe cases
+
+ $this->def = new HTMLPurifier_AttrDef_Integer(false, false, false);
+ $this->assertRange(false, false, false); // allow none
+
+ $this->def = new HTMLPurifier_AttrDef_Integer(true, false, false);
+ $this->assertRange(true, false, false); // negative
+
+ $this->def = new HTMLPurifier_AttrDef_Integer(false, true, false);
+ $this->assertRange(false, true, false); // zero
+
+ $this->def = new HTMLPurifier_AttrDef_Integer(true, true, false);
+ $this->assertRange(true, true, false); // non-positive
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/LangTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/LangTest.php
new file mode 100644
index 000000000..c59175556
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/LangTest.php
@@ -0,0 +1,85 @@
+<?php
+
+class HTMLPurifier_AttrDef_LangTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+
+ $this->def = new HTMLPurifier_AttrDef_Lang();
+
+ // basic good uses
+ $this->assertDef('en');
+ $this->assertDef('en-us');
+
+ $this->assertDef(' en ', 'en'); // trim
+ $this->assertDef('EN', 'en'); // case insensitivity
+
+ // (thanks Eugen Pankratz for noticing the typos!)
+ $this->assertDef('En-Us-Edison', 'en-us-edison'); // complex ci
+
+ $this->assertDef('fr en', false); // multiple languages
+ $this->assertDef('%', false); // bad character
+
+ // test overlong language according to syntax
+ $this->assertDef('thisistoolongsoitgetscut', false);
+
+ // primary subtag rules
+ // I'm somewhat hesitant to allow x and i as primary language codes,
+ // because they usually are never used in real life. However,
+ // theoretically speaking, having them alone is permissable, so
+ // I'll be lenient. No XML parser is going to complain anyway.
+ $this->assertDef('x');
+ $this->assertDef('i');
+ // real world use-cases
+ $this->assertDef('x-klingon');
+ $this->assertDef('i-mingo');
+ // because the RFC only defines two and three letter primary codes,
+ // anything with a length of four or greater is invalid, despite
+ // the syntax stipulation of 1 to 8 characters. Because the RFC
+ // specifically states that this reservation is in order to allow
+ // for future versions to expand, the adoption of a new RFC will
+ // require these test cases to be rewritten, even if backwards-
+ // compatibility is largely retained (i.e. this is not forwards
+ // compatible)
+ $this->assertDef('four', false);
+ // for similar reasons, disallow any other one character language
+ $this->assertDef('f', false);
+
+ // second subtag rules
+ // one letter subtags prohibited until revision. This is, however,
+ // less volatile than the restrictions on the primary subtags.
+ // Also note that this test-case tests fix-behavior: chop
+ // off subtags until you get a valid language code.
+ $this->assertDef('en-a', 'en');
+ // however, x is a reserved single-letter subtag that is allowed
+ $this->assertDef('en-x', 'en-x');
+ // 2-8 chars are permitted, but have special meaning that cannot
+ // be checked without maintaining country code lookup tables (for
+ // two characters) or special registration tables (for all above).
+ $this->assertDef('en-uk', true);
+
+ // further subtag rules: only syntactic constraints
+ $this->assertDef('en-us-edison');
+ $this->assertDef('en-us-toolonghaha', 'en-us');
+ $this->assertDef('en-us-a-silly-long-one');
+
+ // rfc 3066 stipulates that if a three letter and a two letter code
+ // are available, the two letter one MUST be used. Without a language
+ // code lookup table, we cannot implement this functionality.
+
+ // although the HTML protocol, technically speaking, allows you to
+ // omit language tags, this implicitly means that the parent element's
+ // language is the one applicable, which, in some cases, is incorrect.
+ // Thus, we allow und, only slightly defying the RFC's SHOULD NOT
+ // designation.
+ $this->assertDef('und');
+
+ // because attributes only allow one language, mul is allowed, complying
+ // with the RFC's SHOULD NOT designation.
+ $this->assertDef('mul');
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/SwitchTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/SwitchTest.php
new file mode 100644
index 000000000..21bafe697
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/SwitchTest.php
@@ -0,0 +1,34 @@
+<?php
+
+class HTMLPurifier_AttrDef_SwitchTest extends HTMLPurifier_AttrDefHarness
+{
+
+ protected $with, $without;
+
+ function setUp() {
+ parent::setUp();
+ generate_mock_once('HTMLPurifier_AttrDef');
+ $this->with = new HTMLPurifier_AttrDefMock();
+ $this->without = new HTMLPurifier_AttrDefMock();
+ $this->def = new HTMLPurifier_AttrDef_Switch('tag', $this->with, $this->without);
+ }
+
+ function testWith() {
+ $token = new HTMLPurifier_Token_Start('tag');
+ $this->context->register('CurrentToken', $token);
+ $this->with->expectOnce('validate');
+ $this->with->setReturnValue('validate', 'foo');
+ $this->assertDef('bar', 'foo');
+ }
+
+ function testWithout() {
+ $token = new HTMLPurifier_Token_Start('other-tag');
+ $this->context->register('CurrentToken', $token);
+ $this->without->expectOnce('validate');
+ $this->without->setReturnValue('validate', 'foo');
+ $this->assertDef('bar', 'foo');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/TextTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/TextTest.php
new file mode 100644
index 000000000..458008aa8
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/TextTest.php
@@ -0,0 +1,17 @@
+<?php
+
+class HTMLPurifier_AttrDef_TextTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+
+ $this->def = new HTMLPurifier_AttrDef_Text();
+
+ $this->assertDef('This is spiffy text!');
+ $this->assertDef(" Casual\tCDATA parse\ncheck. ", 'Casual CDATA parse check.');
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/Email/SimpleCheckTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/Email/SimpleCheckTest.php
new file mode 100644
index 000000000..c310347e9
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/Email/SimpleCheckTest.php
@@ -0,0 +1,13 @@
+<?php
+
+class HTMLPurifier_AttrDef_URI_Email_SimpleCheckTest
+ extends HTMLPurifier_AttrDef_URI_EmailHarness
+{
+
+ function setUp() {
+ $this->def = new HTMLPurifier_AttrDef_URI_Email_SimpleCheck();
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/EmailHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/EmailHarness.php
new file mode 100644
index 000000000..594e2ce29
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/EmailHarness.php
@@ -0,0 +1,31 @@
+<?php
+
+class HTMLPurifier_AttrDef_URI_EmailHarness extends HTMLPurifier_AttrDefHarness
+{
+
+ /**
+ * Tests common email strings that are obviously pass/fail
+ */
+ function testCore() {
+ $this->assertDef('bob@example.com');
+ $this->assertDef(' bob@example.com ', 'bob@example.com');
+ $this->assertDef('bob.thebuilder@example.net');
+ $this->assertDef('Bob_the_Builder-the-2nd@example.org');
+ $this->assertDef('Bob%20the%20Builder@white-space.test');
+
+ // extended format, with real name
+ //$this->assertDef('Bob%20Builder%20%3Cbobby.bob.bob@it.is.example.com%3E');
+ //$this->assertDef('Bob Builder <bobby.bob.bob@it.is.example.com>');
+
+ // time to fail
+ $this->assertDef('bob', false);
+ $this->assertDef('bob@home@work', false);
+ $this->assertDef('@example.com', false);
+ $this->assertDef('bob@', false);
+ $this->assertDef('', false);
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/HostTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/HostTest.php
new file mode 100644
index 000000000..b5827718b
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/HostTest.php
@@ -0,0 +1,53 @@
+<?php
+
+// takes a URI formatted host and validates it
+
+
+class HTMLPurifier_AttrDef_URI_HostTest extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+
+ $this->def = new HTMLPurifier_AttrDef_URI_Host();
+
+ $this->assertDef('[2001:DB8:0:0:8:800:200C:417A]'); // IPv6
+ $this->assertDef('124.15.6.89'); // IPv4
+ $this->assertDef('www.google.com'); // reg-name
+
+ // more domain name tests
+ $this->assertDef('test.');
+ $this->assertDef('sub.test.');
+ $this->assertDef('.test', false);
+ $this->assertDef('ff');
+ $this->assertDef('1f', false);
+ $this->assertDef('-f', false);
+ $this->assertDef('f1');
+ $this->assertDef('f-', false);
+ $this->assertDef('sub.ff');
+ $this->assertDef('sub.1f', false);
+ $this->assertDef('sub.-f', false);
+ $this->assertDef('sub.f1');
+ $this->assertDef('sub.f-', false);
+ $this->assertDef('ff.top');
+ $this->assertDef('1f.top');
+ $this->assertDef('-f.top', false);
+ $this->assertDef('ff.top');
+ $this->assertDef('f1.top');
+ $this->assertDef('f-.top', false);
+
+ $this->assertDef("\xE4\xB8\xAD\xE6\x96\x87.com.cn", false);
+
+ }
+
+ function testIDNA() {
+ if (!$GLOBALS['HTMLPurifierTest']['Net_IDNA2']) {
+ return false;
+ }
+ $this->config->set('Core.EnableIDNA', true);
+ $this->assertDef("\xE4\xB8\xAD\xE6\x96\x87.com.cn", "xn--fiq228c.com.cn");
+ $this->assertDef("\xe2\x80\x85.com", false); // rejected
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/IPv4Test.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/IPv4Test.php
new file mode 100644
index 000000000..0a4eb17ba
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/IPv4Test.php
@@ -0,0 +1,25 @@
+<?php
+
+// IPv4 test case is spliced from Feyd's IPv6 implementation
+// we ought to disallow non-routable addresses
+
+class HTMLPurifier_AttrDef_URI_IPv4Test extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+
+ $this->def = new HTMLPurifier_AttrDef_URI_IPv4();
+
+ $this->assertDef('127.0.0.1'); // standard IPv4, loopback, non-routable
+ $this->assertDef('0.0.0.0'); // standard IPv4, unspecified, non-routable
+ $this->assertDef('255.255.255.255'); // standard IPv4
+
+ $this->assertDef('300.0.0.0', false); // standard IPv4, out of range
+ $this->assertDef('124.15.6.89/60', false); // standard IPv4, prefix not allowed
+
+ $this->assertDef('', false); // nothing
+
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/IPv6Test.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/IPv6Test.php
new file mode 100644
index 000000000..083e818aa
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/IPv6Test.php
@@ -0,0 +1,43 @@
+<?php
+
+// test case is from Feyd's IPv6 implementation
+// we ought to disallow non-routable addresses
+
+class HTMLPurifier_AttrDef_URI_IPv6Test extends HTMLPurifier_AttrDefHarness
+{
+
+ function test() {
+
+ $this->def = new HTMLPurifier_AttrDef_URI_IPv6();
+
+ $this->assertDef('2001:DB8:0:0:8:800:200C:417A'); // unicast, full
+ $this->assertDef('FF01:0:0:0:0:0:0:101'); // multicast, full
+ $this->assertDef('0:0:0:0:0:0:0:1'); // loopback, full
+ $this->assertDef('0:0:0:0:0:0:0:0'); // unspecified, full
+ $this->assertDef('2001:DB8::8:800:200C:417A'); // unicast, compressed
+ $this->assertDef('FF01::101'); // multicast, compressed
+
+ $this->assertDef('::1'); // loopback, compressed, non-routable
+ $this->assertDef('::'); // unspecified, compressed, non-routable
+ $this->assertDef('0:0:0:0:0:0:13.1.68.3'); // IPv4-compatible IPv6 address, full, deprecated
+ $this->assertDef('0:0:0:0:0:FFFF:129.144.52.38'); // IPv4-mapped IPv6 address, full
+ $this->assertDef('::13.1.68.3'); // IPv4-compatible IPv6 address, compressed, deprecated
+ $this->assertDef('::FFFF:129.144.52.38'); // IPv4-mapped IPv6 address, compressed
+ $this->assertDef('2001:0DB8:0000:CD30:0000:0000:0000:0000/60'); // full, with prefix
+ $this->assertDef('2001:0DB8::CD30:0:0:0:0/60'); // compressed, with prefix
+ $this->assertDef('2001:0DB8:0:CD30::/60'); // compressed, with prefix #2
+ $this->assertDef('::/128'); // compressed, unspecified address type, non-routable
+ $this->assertDef('::1/128'); // compressed, loopback address type, non-routable
+ $this->assertDef('FF00::/8'); // compressed, multicast address type
+ $this->assertDef('FE80::/10'); // compressed, link-local unicast, non-routable
+ $this->assertDef('FEC0::/10'); // compressed, site-local unicast, deprecated
+
+ $this->assertDef('2001:DB8:0:0:8:800:200C:417A:221', false); // unicast, full
+ $this->assertDef('FF01::101::2', false); //multicast, compressed
+ $this->assertDef('', false); // nothing
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URITest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URITest.php
new file mode 100644
index 000000000..3044367a2
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URITest.php
@@ -0,0 +1,146 @@
+<?php
+
+/**
+ * @todo Aim for complete code coverage with mocks
+ */
+class HTMLPurifier_AttrDef_URITest extends HTMLPurifier_AttrDefHarness
+{
+
+ function setUp() {
+ $this->def = new HTMLPurifier_AttrDef_URI();
+ parent::setUp();
+ }
+
+ function testIntegration() {
+ $this->assertDef('http://www.google.com/');
+ $this->assertDef('http:', '');
+ $this->assertDef('http:/foo', '/foo');
+ $this->assertDef('javascript:bad_stuff();', false);
+ $this->assertDef('ftp://www.example.com/');
+ $this->assertDef('news:rec.alt');
+ $this->assertDef('nntp://news.example.com/324234');
+ $this->assertDef('mailto:bob@example.com');
+ }
+
+ function testIntegrationWithPercentEncoder() {
+ $this->assertDef(
+ 'http://www.example.com/%56%fc%GJ%5%FC',
+ 'http://www.example.com/V%FC%25GJ%255%FC'
+ );
+ }
+
+ function testPercentEncoding() {
+ $this->assertDef(
+ 'http:colon:mercenary',
+ 'colon%3Amercenary'
+ );
+ }
+
+ function testPercentEncodingPreserve() {
+ $this->assertDef(
+ 'http://www.example.com/abcABC123-_.!~*()\''
+ );
+ }
+
+ function testEmbeds() {
+ $this->def = new HTMLPurifier_AttrDef_URI(true);
+ $this->assertDef('http://sub.example.com/alas?foo=asd');
+ $this->assertDef('mailto:foo@example.com', false);
+ }
+
+ function testConfigMunge() {
+ $this->config->set('URI.Munge', 'http://www.google.com/url?q=%s');
+ $this->assertDef(
+ 'http://www.example.com/',
+ 'http://www.google.com/url?q=http%3A%2F%2Fwww.example.com%2F'
+ );
+ $this->assertDef('index.html');
+ $this->assertDef('javascript:foobar();', false);
+ }
+
+ function testDefaultSchemeRemovedInBlank() {
+ $this->assertDef('http:', '');
+ }
+
+ function testDefaultSchemeRemovedInRelativeURI() {
+ $this->assertDef('http:/foo/bar', '/foo/bar');
+ }
+
+ function testDefaultSchemeNotRemovedInAbsoluteURI() {
+ $this->assertDef('http://example.com/foo/bar');
+ }
+
+ function testAltSchemeNotRemoved() {
+ $this->assertDef('mailto:this-looks-like-a-path@example.com');
+ }
+
+ function testResolveNullSchemeAmbiguity() {
+ $this->assertDef('///foo', '/foo');
+ }
+
+ function testResolveNullSchemeDoubleAmbiguity() {
+ $this->config->set('URI.Host', 'example.com');
+ $this->assertDef('////foo', '//example.com//foo');
+ }
+
+ function testURIDefinitionValidation() {
+ $parser = new HTMLPurifier_URIParser();
+ $uri = $parser->parse('http://example.com');
+ $this->config->set('URI.DefinitionID', 'HTMLPurifier_AttrDef_URITest->testURIDefinitionValidation');
+
+ generate_mock_once('HTMLPurifier_URIDefinition');
+ $uri_def = new HTMLPurifier_URIDefinitionMock();
+ $uri_def->expectOnce('filter', array($uri, '*', '*'));
+ $uri_def->setReturnValue('filter', true, array($uri, '*', '*'));
+ $uri_def->expectOnce('postFilter', array($uri, '*', '*'));
+ $uri_def->setReturnValue('postFilter', true, array($uri, '*', '*'));
+ $uri_def->setup = true;
+
+ // Since definitions are no longer passed by reference, we need
+ // to muck around with the cache to insert our mock. This is
+ // technically a little bad, since the cache shouldn't change
+ // behavior, but I don't feel too good about letting users
+ // overload entire definitions.
+ generate_mock_once('HTMLPurifier_DefinitionCache');
+ $cache_mock = new HTMLPurifier_DefinitionCacheMock();
+ $cache_mock->setReturnValue('get', $uri_def);
+
+ generate_mock_once('HTMLPurifier_DefinitionCacheFactory');
+ $factory_mock = new HTMLPurifier_DefinitionCacheFactoryMock();
+ $old = HTMLPurifier_DefinitionCacheFactory::instance();
+ HTMLPurifier_DefinitionCacheFactory::instance($factory_mock);
+ $factory_mock->setReturnValue('create', $cache_mock);
+
+ $this->assertDef('http://example.com');
+
+ HTMLPurifier_DefinitionCacheFactory::instance($old);
+ }
+
+ function test_make() {
+ $factory = new HTMLPurifier_AttrDef_URI();
+ $def = $factory->make('');
+ $def2 = new HTMLPurifier_AttrDef_URI();
+ $this->assertIdentical($def, $def2);
+
+ $def = $factory->make('embedded');
+ $def2 = new HTMLPurifier_AttrDef_URI(true);
+ $this->assertIdentical($def, $def2);
+ }
+
+ /*
+ function test_validate_configWhitelist() {
+
+ $this->config->set('URI.HostPolicy', 'DenyAll');
+ $this->config->set('URI.HostWhitelist', array(null, 'google.com'));
+
+ $this->assertDef('http://example.com/fo/google.com', false);
+ $this->assertDef('server.txt');
+ $this->assertDef('ftp://www.google.com/?t=a');
+ $this->assertDef('http://google.com.tricky.spamsite.net', false);
+
+ }
+ */
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDefHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDefHarness.php
new file mode 100644
index 000000000..b45b0ca53
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDefHarness.php
@@ -0,0 +1,27 @@
+<?php
+
+class HTMLPurifier_AttrDefHarness extends HTMLPurifier_Harness
+{
+
+ protected $def;
+ protected $context, $config;
+
+ public function setUp() {
+ $this->config = HTMLPurifier_Config::createDefault();
+ $this->context = new HTMLPurifier_Context();
+ }
+
+ // cannot be used for accumulator
+ function assertDef($string, $expect = true) {
+ // $expect can be a string or bool
+ $result = $this->def->validate($string, $this->config, $this->context);
+ if ($expect === true) {
+ $this->assertIdentical($string, $result);
+ } else {
+ $this->assertIdentical($expect, $result);
+ }
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDefTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDefTest.php
new file mode 100644
index 000000000..d7466e37d
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDefTest.php
@@ -0,0 +1,32 @@
+<?php
+
+Mock::generatePartial(
+ 'HTMLPurifier_AttrDef',
+ 'HTMLPurifier_AttrDefTestable',
+ array('validate'));
+
+class HTMLPurifier_AttrDefTest extends HTMLPurifier_Harness
+{
+
+ function test_parseCDATA() {
+
+ $def = new HTMLPurifier_AttrDefTestable();
+
+ $this->assertIdentical('', $def->parseCDATA(''));
+ $this->assertIdentical('', $def->parseCDATA("\t\n\r \t\t"));
+ $this->assertIdentical('foo', $def->parseCDATA("\t\n\r foo\t\t"));
+ $this->assertIdentical('translate to space', $def->parseCDATA("translate\nto\tspace"));
+
+ }
+
+ function test_make() {
+
+ $def = new HTMLPurifier_AttrDefTestable();
+ $def2 = $def->make('');
+ $this->assertIdentical($def, $def2);
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BackgroundTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BackgroundTest.php
new file mode 100644
index 000000000..0730ab4bc
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BackgroundTest.php
@@ -0,0 +1,40 @@
+<?php
+
+class HTMLPurifier_AttrTransform_BackgroundTest extends HTMLPurifier_AttrTransformHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_AttrTransform_Background();
+ }
+
+ function testEmptyInput() {
+ $this->assertResult( array() );
+ }
+
+ function testBasicTransform() {
+ $this->assertResult(
+ array('background' => 'logo.png'),
+ array('style' => 'background-image:url(logo.png);')
+ );
+ }
+
+ function testPrependNewCSS() {
+ $this->assertResult(
+ array('background' => 'logo.png', 'style' => 'font-weight:bold'),
+ array('style' => 'background-image:url(logo.png);font-weight:bold')
+ );
+ }
+
+ function testLenientTreatmentOfInvalidInput() {
+ // notice that we rely on the CSS validator later to fix this invalid
+ // stuff
+ $this->assertResult(
+ array('background' => 'logo.png);foo:('),
+ array('style' => 'background-image:url(logo.png);foo:();')
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BdoDirTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BdoDirTest.php
new file mode 100644
index 000000000..cdf6f8a9b
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BdoDirTest.php
@@ -0,0 +1,30 @@
+<?php
+
+class HTMLPurifier_AttrTransform_BdoDirTest extends HTMLPurifier_AttrTransformHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_AttrTransform_BdoDir();
+ }
+
+ function testAddDefaultDir() {
+ $this->assertResult( array(), array('dir' => 'ltr') );
+ }
+
+ function testPreserveExistingDir() {
+ $this->assertResult( array('dir' => 'rtl') );
+ }
+
+ function testAlternateDefault() {
+ $this->config->set('Attr.DefaultTextDir', 'rtl');
+ $this->assertResult(
+ array(),
+ array('dir' => 'rtl')
+ );
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BgColorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BgColorTest.php
new file mode 100644
index 000000000..13567b74e
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BgColorTest.php
@@ -0,0 +1,44 @@
+<?php
+
+// we currently rely on the CSS validator to fix any problems.
+// This means that this transform, strictly speaking, supports
+// a superset of the functionality.
+
+class HTMLPurifier_AttrTransform_BgColorTest extends HTMLPurifier_AttrTransformHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_AttrTransform_BgColor();
+ }
+
+ function testEmptyInput() {
+ $this->assertResult( array() );
+ }
+
+ function testBasicTransform() {
+ $this->assertResult(
+ array('bgcolor' => '#000000'),
+ array('style' => 'background-color:#000000;')
+ );
+ }
+
+ function testPrependNewCSS() {
+ $this->assertResult(
+ array('bgcolor' => '#000000', 'style' => 'font-weight:bold'),
+ array('style' => 'background-color:#000000;font-weight:bold')
+ );
+ }
+
+ function testLenientTreatmentOfInvalidInput() {
+ // this may change when we natively support the datatype and
+ // validate its contents before forwarding it on
+ $this->assertResult(
+ array('bgcolor' => '#F00'),
+ array('style' => 'background-color:#F00;')
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BoolToCSSTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BoolToCSSTest.php
new file mode 100644
index 000000000..73f9d6b86
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BoolToCSSTest.php
@@ -0,0 +1,38 @@
+<?php
+
+class HTMLPurifier_AttrTransform_BoolToCSSTest extends HTMLPurifier_AttrTransformHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_AttrTransform_BoolToCSS('foo', 'bar:3in;');
+ }
+
+ function testEmptyInput() {
+ $this->assertResult( array() );
+ }
+
+ function testBasicTransform() {
+ $this->assertResult(
+ array('foo' => 'foo'),
+ array('style' => 'bar:3in;')
+ );
+ }
+
+ function testIgnoreValueOfBooleanAttribute() {
+ $this->assertResult(
+ array('foo' => 'no'),
+ array('style' => 'bar:3in;')
+ );
+ }
+
+ function testPrependCSS() {
+ $this->assertResult(
+ array('foo' => 'foo', 'style' => 'background-color:#F00;'),
+ array('style' => 'bar:3in;background-color:#F00;')
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BorderTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BorderTest.php
new file mode 100644
index 000000000..d10aa28e6
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BorderTest.php
@@ -0,0 +1,38 @@
+<?php
+
+class HTMLPurifier_AttrTransform_BorderTest extends HTMLPurifier_AttrTransformHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_AttrTransform_Border();
+ }
+
+ function testEmptyInput() {
+ $this->assertResult( array() );
+ }
+
+ function testBasicTransform() {
+ $this->assertResult(
+ array('border' => '1'),
+ array('style' => 'border:1px solid;')
+ );
+ }
+
+ function testLenientTreatmentOfInvalidInput() {
+ $this->assertResult(
+ array('border' => '10%'),
+ array('style' => 'border:10%px solid;')
+ );
+ }
+
+ function testPrependNewCSS() {
+ $this->assertResult(
+ array('border' => '23', 'style' => 'font-weight:bold;'),
+ array('style' => 'border:23px solid;font-weight:bold;')
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/EnumToCSSTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/EnumToCSSTest.php
new file mode 100644
index 000000000..f0381fe88
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/EnumToCSSTest.php
@@ -0,0 +1,73 @@
+<?php
+
+class HTMLPurifier_AttrTransform_EnumToCSSTest extends HTMLPurifier_AttrTransformHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_AttrTransform_EnumToCSS('align', array(
+ 'left' => 'text-align:left;',
+ 'right' => 'text-align:right;'
+ ));
+ }
+
+ function testEmptyInput() {
+ $this->assertResult( array() );
+ }
+
+ function testPreserveArraysWithoutInterestingAttributes() {
+ $this->assertResult( array('style' => 'font-weight:bold;') );
+ }
+
+ function testConvertAlignLeft() {
+ $this->assertResult(
+ array('align' => 'left'),
+ array('style' => 'text-align:left;')
+ );
+ }
+
+ function testConvertAlignRight() {
+ $this->assertResult(
+ array('align' => 'right'),
+ array('style' => 'text-align:right;')
+ );
+ }
+
+ function testRemoveInvalidAlign() {
+ $this->assertResult(
+ array('align' => 'invalid'),
+ array()
+ );
+ }
+
+ function testPrependNewCSS() {
+ $this->assertResult(
+ array('align' => 'left', 'style' => 'font-weight:bold;'),
+ array('style' => 'text-align:left;font-weight:bold;')
+ );
+
+ }
+
+ function testCaseInsensitive() {
+ $this->obj = new HTMLPurifier_AttrTransform_EnumToCSS('align', array(
+ 'right' => 'text-align:right;'
+ ));
+ $this->assertResult(
+ array('align' => 'RIGHT'),
+ array('style' => 'text-align:right;')
+ );
+ }
+
+ function testCaseSensitive() {
+ $this->obj = new HTMLPurifier_AttrTransform_EnumToCSS('align', array(
+ 'right' => 'text-align:right;'
+ ), true);
+ $this->assertResult(
+ array('align' => 'RIGHT'),
+ array()
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/ImgRequiredTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/ImgRequiredTest.php
new file mode 100644
index 000000000..99f0a03e9
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/ImgRequiredTest.php
@@ -0,0 +1,55 @@
+<?php
+
+class HTMLPurifier_AttrTransform_ImgRequiredTest extends HTMLPurifier_AttrTransformHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_AttrTransform_ImgRequired();
+ }
+
+ function testAddMissingAttr() {
+ $this->config->set('Core.RemoveInvalidImg', false);
+ $this->assertResult(
+ array(),
+ array('src' => '', 'alt' => 'Invalid image')
+ );
+ }
+
+ function testAlternateDefaults() {
+ $this->config->set('Attr.DefaultInvalidImage', 'blank.png');
+ $this->config->set('Attr.DefaultInvalidImageAlt', 'Pawned!');
+ $this->config->set('Attr.DefaultImageAlt', 'not pawned');
+ $this->config->set('Core.RemoveInvalidImg', false);
+ $this->assertResult(
+ array(),
+ array('src' => 'blank.png', 'alt' => 'Pawned!')
+ );
+ }
+
+ function testGenerateAlt() {
+ $this->assertResult(
+ array('src' => '/path/to/foobar.png'),
+ array('src' => '/path/to/foobar.png', 'alt' => 'foobar.png')
+ );
+ }
+
+ function testAddDefaultSrc() {
+ $this->config->set('Core.RemoveInvalidImg', false);
+ $this->assertResult(
+ array('alt' => 'intrigue'),
+ array('alt' => 'intrigue', 'src' => '')
+ );
+ }
+
+ function testAddDefaultAlt() {
+ $this->config->set('Attr.DefaultImageAlt', 'default');
+ $this->assertResult(
+ array('src' => ''),
+ array('src' => '', 'alt' => 'default')
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/ImgSpaceTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/ImgSpaceTest.php
new file mode 100644
index 000000000..42e8738e4
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/ImgSpaceTest.php
@@ -0,0 +1,55 @@
+<?php
+
+class HTMLPurifier_AttrTransform_ImgSpaceTest extends HTMLPurifier_AttrTransformHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_AttrTransform_ImgSpace('vspace');
+ }
+
+ function testEmptyInput() {
+ $this->assertResult( array() );
+ }
+
+ function testVerticalBasicUsage() {
+ $this->assertResult(
+ array('vspace' => '1'),
+ array('style' => 'margin-top:1px;margin-bottom:1px;')
+ );
+ }
+
+ function testLenientHandlingOfInvalidInput() {
+ $this->assertResult(
+ array('vspace' => '10%'),
+ array('style' => 'margin-top:10%px;margin-bottom:10%px;')
+ );
+ }
+
+ function testPrependNewCSS() {
+ $this->assertResult(
+ array('vspace' => '23', 'style' => 'font-weight:bold;'),
+ array('style' => 'margin-top:23px;margin-bottom:23px;font-weight:bold;')
+ );
+ }
+
+ function testHorizontalBasicUsage() {
+ $this->obj = new HTMLPurifier_AttrTransform_ImgSpace('hspace');
+ $this->assertResult(
+ array('hspace' => '1'),
+ array('style' => 'margin-left:1px;margin-right:1px;')
+ );
+ }
+
+ function testInvalidConstructionParameter() {
+ $this->expectError('ispace is not valid space attribute');
+ $this->obj = new HTMLPurifier_AttrTransform_ImgSpace('ispace');
+ $this->assertResult(
+ array('ispace' => '1'),
+ array()
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/InputTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/InputTest.php
new file mode 100644
index 000000000..2603ff1be
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/InputTest.php
@@ -0,0 +1,94 @@
+<?php
+
+class HTMLPurifier_AttrTransform_InputTest extends HTMLPurifier_AttrTransformHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_AttrTransform_Input();
+ }
+
+ function testEmptyInput() {
+ $this->assertResult(array());
+ }
+
+ function testInvalidCheckedWithEmpty() {
+ $this->assertResult(array('checked' => 'checked'), array());
+ }
+
+ function testInvalidCheckedWithPassword() {
+ $this->assertResult(array(
+ 'checked' => 'checked',
+ 'type' => 'password'
+ ), array(
+ 'type' => 'password'
+ ));
+ }
+
+ function testValidCheckedWithUcCheckbox() {
+ $this->assertResult(array(
+ 'checked' => 'checked',
+ 'type' => 'CHECKBOX',
+ 'value' => 'bar',
+ ));
+ }
+
+ function testInvalidMaxlength() {
+ $this->assertResult(array(
+ 'maxlength' => '10',
+ 'type' => 'checkbox',
+ 'value' => 'foo',
+ ), array(
+ 'type' => 'checkbox',
+ 'value' => 'foo',
+ ));
+ }
+
+ function testValidMaxLength() {
+ $this->assertResult(array(
+ 'maxlength' => '10',
+ ));
+ }
+
+ // these two are really bad test-cases
+
+ function testSizeWithCheckbox() {
+ $this->assertResult(array(
+ 'type' => 'checkbox',
+ 'value' => 'foo',
+ 'size' => '100px',
+ ), array(
+ 'type' => 'checkbox',
+ 'value' => 'foo',
+ 'size' => '100',
+ ));
+ }
+
+ function testSizeWithText() {
+ $this->assertResult(array(
+ 'type' => 'password',
+ 'size' => '100px', // spurious value, to indicate no validation takes place
+ ), array(
+ 'type' => 'password',
+ 'size' => '100px',
+ ));
+ }
+
+ function testInvalidSrc() {
+ $this->assertResult(array(
+ 'src' => 'img.png',
+ ), array());
+ }
+
+ function testMissingValue() {
+ $this->assertResult(array(
+ 'type' => 'checkbox',
+ ), array(
+ 'type' => 'checkbox',
+ 'value' => '',
+ ));
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/LangTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/LangTest.php
new file mode 100644
index 000000000..960ad20a0
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/LangTest.php
@@ -0,0 +1,46 @@
+<?php
+
+class HTMLPurifier_AttrTransform_LangTest
+ extends HTMLPurifier_AttrTransformHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_AttrTransform_Lang();
+ }
+
+ function testEmptyInput() {
+ $this->assertResult(array());
+ }
+
+ function testCopyLangToXMLLang() {
+ $this->assertResult(
+ array('lang' => 'en'),
+ array('lang' => 'en', 'xml:lang' => 'en')
+ );
+ }
+
+ function testPreserveAttributes() {
+ $this->assertResult(
+ array('src' => 'vert.png', 'lang' => 'fr'),
+ array('src' => 'vert.png', 'lang' => 'fr', 'xml:lang' => 'fr')
+ );
+ }
+
+ function testCopyXMLLangToLang() {
+ $this->assertResult(
+ array('xml:lang' => 'en'),
+ array('xml:lang' => 'en', 'lang' => 'en')
+ );
+ }
+
+ function testXMLLangOverridesLang() {
+ $this->assertResult(
+ array('lang' => 'fr', 'xml:lang' => 'de'),
+ array('lang' => 'de', 'xml:lang' => 'de')
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/LengthTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/LengthTest.php
new file mode 100644
index 000000000..3fbaa0ccf
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/LengthTest.php
@@ -0,0 +1,45 @@
+<?php
+
+class HTMLPurifier_AttrTransform_LengthTest extends HTMLPurifier_AttrTransformHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_AttrTransform_Length('width');
+ }
+
+ function testEmptyInput() {
+ $this->assertResult( array() );
+ }
+
+ function testTransformPixel() {
+ $this->assertResult(
+ array('width' => '10'),
+ array('style' => 'width:10px;')
+ );
+ }
+
+ function testTransformPercentage() {
+ $this->assertResult(
+ array('width' => '10%'),
+ array('style' => 'width:10%;')
+ );
+ }
+
+ function testPrependNewCSS() {
+ $this->assertResult(
+ array('width' => '10%', 'style' => 'font-weight:bold'),
+ array('style' => 'width:10%;font-weight:bold')
+ );
+ }
+
+ function testLenientTreatmentOfInvalidInput() {
+ $this->assertResult(
+ array('width' => 'asdf'),
+ array('style' => 'width:asdf;')
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/NameSyncTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/NameSyncTest.php
new file mode 100644
index 000000000..bae4a8d03
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/NameSyncTest.php
@@ -0,0 +1,40 @@
+<?php
+
+class HTMLPurifier_AttrTransform_NameSyncTest extends HTMLPurifier_AttrTransformHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_AttrTransform_NameSync();
+ $this->accumulator = new HTMLPurifier_IDAccumulator();
+ $this->context->register('IDAccumulator', $this->accumulator);
+ $this->config->set('Attr.EnableID', true);
+ }
+
+ function testEmpty() {
+ $this->assertResult( array() );
+ }
+
+ function testAllowSame() {
+ $this->assertResult(
+ array('name' => 'free', 'id' => 'free')
+ );
+ }
+
+ function testAllowDifferent() {
+ $this->assertResult(
+ array('name' => 'tryit', 'id' => 'thisgood')
+ );
+ }
+
+ function testCheckName() {
+ $this->accumulator->add('notok');
+ $this->assertResult(
+ array('name' => 'notok', 'id' => 'ok'),
+ array('id' => 'ok')
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/NameTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/NameTest.php
new file mode 100644
index 000000000..10e121238
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/NameTest.php
@@ -0,0 +1,31 @@
+<?php
+
+class HTMLPurifier_AttrTransform_NameTest extends HTMLPurifier_AttrTransformHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_AttrTransform_Name();
+ }
+
+ function testEmpty() {
+ $this->assertResult( array() );
+ }
+
+ function testTransformNameToID() {
+ $this->assertResult(
+ array('name' => 'free'),
+ array('id' => 'free')
+ );
+ }
+
+ function testExistingIDOverridesName() {
+ $this->assertResult(
+ array('name' => 'tryit', 'id' => 'tobad'),
+ array('id' => 'tobad')
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransformHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransformHarness.php
new file mode 100644
index 000000000..d43c0108f
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransformHarness.php
@@ -0,0 +1,13 @@
+<?php
+
+class HTMLPurifier_AttrTransformHarness extends HTMLPurifier_ComplexHarness
+{
+
+ public function setUp() {
+ parent::setUp();
+ $this->func = 'transform';
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransformTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransformTest.php
new file mode 100644
index 000000000..71a788580
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransformTest.php
@@ -0,0 +1,45 @@
+<?php
+
+Mock::generatePartial(
+ 'HTMLPurifier_AttrTransform',
+ 'HTMLPurifier_AttrTransformTestable',
+ array('transform'));
+
+class HTMLPurifier_AttrTransformTest extends HTMLPurifier_Harness
+{
+
+ function test_prependCSS() {
+
+ $t = new HTMLPurifier_AttrTransformTestable();
+
+ $attr = array();
+ $t->prependCSS($attr, 'style:new;');
+ $this->assertIdentical(array('style' => 'style:new;'), $attr);
+
+ $attr = array('style' => 'style:original;');
+ $t->prependCSS($attr, 'style:new;');
+ $this->assertIdentical(array('style' => 'style:new;style:original;'), $attr);
+
+ $attr = array('style' => 'style:original;', 'misc' => 'un-related');
+ $t->prependCSS($attr, 'style:new;');
+ $this->assertIdentical(array('style' => 'style:new;style:original;', 'misc' => 'un-related'), $attr);
+
+ }
+
+ function test_confiscateAttr() {
+
+ $t = new HTMLPurifier_AttrTransformTestable();
+
+ $attr = array('flavor' => 'sweet');
+ $this->assertIdentical('sweet', $t->confiscateAttr($attr, 'flavor'));
+ $this->assertIdentical(array(), $attr);
+
+ $attr = array('flavor' => 'sweet');
+ $this->assertIdentical(null, $t->confiscateAttr($attr, 'color'));
+ $this->assertIdentical(array('flavor' => 'sweet'), $attr);
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTypesTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTypesTest.php
new file mode 100644
index 000000000..d1ae43709
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTypesTest.php
@@ -0,0 +1,26 @@
+<?php
+
+class HTMLPurifier_AttrTypesTest extends HTMLPurifier_Harness
+{
+
+ function test_get() {
+ $types = new HTMLPurifier_AttrTypes();
+
+ $this->assertIdentical(
+ $types->get('CDATA'),
+ new HTMLPurifier_AttrDef_Text()
+ );
+
+ $this->expectError('Cannot retrieve undefined attribute type foobar');
+ $types->get('foobar');
+
+ $this->assertIdentical(
+ $types->get('Enum#foo,bar'),
+ new HTMLPurifier_AttrDef_Enum(array('foo', 'bar'))
+ );
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrValidator_ErrorsTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrValidator_ErrorsTest.php
new file mode 100644
index 000000000..307d3292b
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrValidator_ErrorsTest.php
@@ -0,0 +1,66 @@
+<?php
+
+class HTMLPurifier_AttrValidator_ErrorsTest extends HTMLPurifier_ErrorsHarness
+{
+
+ public function setup() {
+ parent::setup();
+ $config = HTMLPurifier_Config::createDefault();
+ $this->language = HTMLPurifier_LanguageFactory::instance()->create($config, $this->context);
+ $this->context->register('Locale', $this->language);
+ $this->collector = new HTMLPurifier_ErrorCollector($this->context);
+ $this->context->register('Generator', new HTMLPurifier_Generator($config, $this->context));
+ }
+
+ protected function invoke($input) {
+ $validator = new HTMLPurifier_AttrValidator();
+ $validator->validateToken($input, $this->config, $this->context);
+ }
+
+ function testAttributesTransformedGlobalPre() {
+ $def = $this->config->getHTMLDefinition(true);
+ generate_mock_once('HTMLPurifier_AttrTransform');
+ $transform = new HTMLPurifier_AttrTransformMock();
+ $input = array('original' => 'value');
+ $output = array('class' => 'value'); // must be valid
+ $transform->setReturnValue('transform', $output, array($input, new AnythingExpectation(), new AnythingExpectation()));
+ $def->info_attr_transform_pre[] = $transform;
+
+ $token = new HTMLPurifier_Token_Start('span', $input, 1);
+ $this->invoke($token);
+
+ $result = $this->collector->getRaw();
+ $expect = array(
+ array(1, E_NOTICE, 'Attributes on <span> transformed from original to class', array()),
+ );
+ $this->assertIdentical($result, $expect);
+ }
+
+ function testAttributesTransformedLocalPre() {
+ $this->config->set('HTML.TidyLevel', 'heavy');
+ $input = array('align' => 'right');
+ $output = array('style' => 'text-align:right;');
+ $token = new HTMLPurifier_Token_Start('p', $input, 1);
+ $this->invoke($token);
+ $result = $this->collector->getRaw();
+ $expect = array(
+ array(1, E_NOTICE, 'Attributes on <p> transformed from align to style', array()),
+ );
+ $this->assertIdentical($result, $expect);
+ }
+
+ // too lazy to check for global post and global pre
+
+ function testAttributeRemoved() {
+ $token = new HTMLPurifier_Token_Start('p', array('foobar' => 'right'), 1);
+ $this->invoke($token);
+ $result = $this->collector->getRaw();
+ $expect = array(
+ array(1, E_ERROR, 'foobar attribute on <p> removed', array()),
+ );
+ $this->assertIdentical($result, $expect);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/ChameleonTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/ChameleonTest.php
new file mode 100644
index 000000000..82493f40e
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/ChameleonTest.php
@@ -0,0 +1,40 @@
+<?php
+
+class HTMLPurifier_ChildDef_ChameleonTest extends HTMLPurifier_ChildDefHarness
+{
+
+ protected $isInline;
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_ChildDef_Chameleon(
+ 'b | i', // allowed only when in inline context
+ 'b | i | div' // allowed only when in block context
+ );
+ $this->context->register('IsInline', $this->isInline);
+ }
+
+ function testInlineAlwaysAllowed() {
+ $this->isInline = true;
+ $this->assertResult(
+ '<b>Allowed.</b>'
+ );
+ }
+
+ function testBlockNotAllowedInInline() {
+ $this->isInline = true;
+ $this->assertResult(
+ '<div>Not allowed.</div>', ''
+ );
+ }
+
+ function testBlockAllowedInNonInline() {
+ $this->isInline = false;
+ $this->assertResult(
+ '<div>Allowed.</div>'
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/CustomTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/CustomTest.php
new file mode 100644
index 000000000..5b138a3c1
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/CustomTest.php
@@ -0,0 +1,89 @@
+<?php
+
+class HTMLPurifier_ChildDef_CustomTest extends HTMLPurifier_ChildDefHarness
+{
+
+ function test() {
+
+ $this->obj = new HTMLPurifier_ChildDef_Custom('(a,b?,c*,d+,(a,b)*)');
+
+ $this->assertEqual($this->obj->elements, array('a' => true,
+ 'b' => true, 'c' => true, 'd' => true));
+
+ $this->assertResult('', false);
+ $this->assertResult('<a /><a />', false);
+
+ $this->assertResult('<a /><b /><c /><d /><a /><b />');
+ $this->assertResult('<a /><d>Dob</d><a /><b>foo</b>'.
+ '<a href="moo" /><b>foo</b>');
+
+ }
+
+ function testNesting() {
+ $this->obj = new HTMLPurifier_ChildDef_Custom('(a,b,(c|d))+');
+ $this->assertEqual($this->obj->elements, array('a' => true,
+ 'b' => true, 'c' => true, 'd' => true));
+ $this->assertResult('', false);
+ $this->assertResult('<a /><b /><c /><a /><b /><d />');
+ $this->assertResult('<a /><b /><c /><d />', false);
+ }
+
+ function testNestedEitherOr() {
+ $this->obj = new HTMLPurifier_ChildDef_Custom('b,(a|(c|d))+');
+ $this->assertEqual($this->obj->elements, array('a' => true,
+ 'b' => true, 'c' => true, 'd' => true));
+ $this->assertResult('', false);
+ $this->assertResult('<b /><a /><c /><d />');
+ $this->assertResult('<b /><d /><a /><a />');
+ $this->assertResult('<b /><a />');
+ $this->assertResult('<acd />', false);
+ }
+
+ function testNestedQuantifier() {
+ $this->obj = new HTMLPurifier_ChildDef_Custom('(b,c+)*');
+ $this->assertEqual($this->obj->elements, array('b' => true, 'c' => true));
+ $this->assertResult('');
+ $this->assertResult('<b /><c />');
+ $this->assertResult('<b /><c /><c /><c />');
+ $this->assertResult('<b /><c /><b /><c />');
+ $this->assertResult('<b /><c /><b />', false);
+ }
+
+ function testEitherOr() {
+
+ $this->obj = new HTMLPurifier_ChildDef_Custom('a|b');
+ $this->assertEqual($this->obj->elements, array('a' => true, 'b' => true));
+ $this->assertResult('', false);
+ $this->assertResult('<a />');
+ $this->assertResult('<b />');
+ $this->assertResult('<a /><b />', false);
+
+ }
+
+ function testCommafication() {
+
+ $this->obj = new HTMLPurifier_ChildDef_Custom('a,b');
+ $this->assertEqual($this->obj->elements, array('a' => true, 'b' => true));
+ $this->assertResult('<a /><b />');
+ $this->assertResult('<ab />', false);
+
+ }
+
+ function testPcdata() {
+ $this->obj = new HTMLPurifier_ChildDef_Custom('#PCDATA,a');
+ $this->assertEqual($this->obj->elements, array('#PCDATA' => true, 'a' => true));
+ $this->assertResult('foo<a />');
+ $this->assertResult('<a />', false);
+ }
+
+ function testWhitespace() {
+ $this->obj = new HTMLPurifier_ChildDef_Custom('a');
+ $this->assertEqual($this->obj->elements, array('a' => true));
+ $this->assertResult('foo<a />', false);
+ $this->assertResult('<a />');
+ $this->assertResult(' <a />');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/ListTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/ListTest.php
new file mode 100644
index 000000000..02dcab0fb
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/ListTest.php
@@ -0,0 +1,50 @@
+<?php
+
+class HTMLPurifier_ChildDef_ListTest extends HTMLPurifier_ChildDefHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_ChildDef_List();
+ }
+
+ function testEmptyInput() {
+ $this->assertResult('', false);
+ }
+
+ function testSingleLi() {
+ $this->assertResult('<li />');
+ }
+
+ function testSomeLi() {
+ $this->assertResult('<li>asdf</li><li />');
+ }
+
+ function testIllegal() {
+ // XXX actually this never gets triggered in practice
+ $this->assertResult('<li /><b />', '<li /><li><b /></li>');
+ }
+
+ function testOlAtBeginning() {
+ $this->assertResult('<ol />', '<li><ol /></li>');
+ }
+
+ function testOlAtBeginningWithOtherJunk() {
+ $this->assertResult('<ol /><li />', '<li><ol /></li><li />');
+ }
+
+ function testOlInMiddle() {
+ $this->assertResult('<li>Foo</li><ol><li>Bar</li></ol>', '<li>Foo<ol><li>Bar</li></ol></li>');
+ }
+
+ function testMultipleOl() {
+ $this->assertResult('<li /><ol /><ol />', '<li><ol /><ol /></li>');
+ }
+
+ function testUlAtBeginning() {
+ $this->assertResult('<ul />', '<li><ul /></li>');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/OptionalTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/OptionalTest.php
new file mode 100644
index 000000000..a5f34f7b1
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/OptionalTest.php
@@ -0,0 +1,33 @@
+<?php
+
+class HTMLPurifier_ChildDef_OptionalTest extends HTMLPurifier_ChildDefHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_ChildDef_Optional('b | i');
+ }
+
+ function testBasicUsage() {
+ $this->assertResult('<b>Bold text</b><img />', '<b>Bold text</b>');
+ }
+
+ function testRemoveForbiddenText() {
+ $this->assertResult('Not allowed text', '');
+ }
+
+ function testEmpty() {
+ $this->assertResult('');
+ }
+
+ function testWhitespace() {
+ $this->assertResult(' ');
+ }
+
+ function testMultipleWhitespace() {
+ $this->assertResult(' ');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/RequiredTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/RequiredTest.php
new file mode 100644
index 000000000..8bb4f45ee
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/RequiredTest.php
@@ -0,0 +1,73 @@
+<?php
+
+class HTMLPurifier_ChildDef_RequiredTest extends HTMLPurifier_ChildDefHarness
+{
+
+ function testPrepareString() {
+ $def = new HTMLPurifier_ChildDef_Required('foobar | bang |gizmo');
+ $this->assertIdentical($def->elements,
+ array(
+ 'foobar' => true
+ ,'bang' => true
+ ,'gizmo' => true
+ ));
+ }
+
+ function testPrepareArray() {
+ $def = new HTMLPurifier_ChildDef_Required(array('href', 'src'));
+ $this->assertIdentical($def->elements,
+ array(
+ 'href' => true
+ ,'src' => true
+ ));
+ }
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_ChildDef_Required('dt | dd');
+ }
+
+ function testEmptyInput() {
+ $this->assertResult('', false);
+ }
+
+ function testRemoveIllegalTagsAndElements() {
+ $this->assertResult(
+ '<dt>Term</dt>Text in an illegal location'.
+ '<dd>Definition</dd><b>Illegal tag</b>',
+ '<dt>Term</dt><dd>Definition</dd>');
+ $this->assertResult('How do you do!', false);
+ }
+
+ function testIgnoreWhitespace() {
+ // whitespace shouldn't trigger it
+ $this->assertResult("\n<dd>Definition</dd> ");
+ }
+
+ function testPreserveWhitespaceAfterRemoval() {
+ $this->assertResult(
+ '<dd>Definition</dd> <b></b> ',
+ '<dd>Definition</dd> '
+ );
+ }
+
+ function testDeleteNodeIfOnlyWhitespace() {
+ $this->assertResult("\t ", false);
+ }
+
+ function testPCDATAAllowed() {
+ $this->obj = new HTMLPurifier_ChildDef_Required('#PCDATA | b');
+ $this->assertResult('Out <b>Bold text</b><img />', 'Out <b>Bold text</b>');
+ }
+
+ function testPCDATAAllowedWithEscaping() {
+ $this->obj = new HTMLPurifier_ChildDef_Required('#PCDATA | b');
+ $this->config->set('Core.EscapeInvalidChildren', true);
+ $this->assertResult(
+ 'Out <b>Bold text</b><img />',
+ 'Out <b>Bold text</b>&lt;img /&gt;'
+ );
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/StrictBlockquoteTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/StrictBlockquoteTest.php
new file mode 100644
index 000000000..52594b1a0
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/StrictBlockquoteTest.php
@@ -0,0 +1,83 @@
+<?php
+
+class HTMLPurifier_ChildDef_StrictBlockquoteTest
+extends HTMLPurifier_ChildDefHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_ChildDef_StrictBlockquote('div | p');
+ }
+
+ function testEmptyInput() {
+ $this->assertResult('');
+ }
+
+ function testPreserveValidP() {
+ $this->assertResult('<p>Valid</p>');
+ }
+
+ function testPreserveValidDiv() {
+ $this->assertResult('<div>Still valid</div>');
+ }
+
+ function testWrapTextWithP() {
+ $this->assertResult('Needs wrap', '<p>Needs wrap</p>');
+ }
+
+ function testNoWrapForWhitespaceOrValidElements() {
+ $this->assertResult('<p>Do not wrap</p> <p>Whitespace</p>');
+ }
+
+ function testWrapTextNextToValidElements() {
+ $this->assertResult(
+ 'Wrap'. '<p>Do not wrap</p>',
+ '<p>Wrap</p><p>Do not wrap</p>'
+ );
+ }
+
+ function testWrapInlineElements() {
+ $this->assertResult(
+ '<p>Do not</p>'.'<b>Wrap</b>',
+ '<p>Do not</p><p><b>Wrap</b></p>'
+ );
+ }
+
+ function testWrapAndRemoveInvalidTags() {
+ $this->assertResult(
+ '<li>Not allowed</li>Paragraph.<p>Hmm.</p>',
+ '<p>Not allowedParagraph.</p><p>Hmm.</p>'
+ );
+ }
+
+ function testWrapComplicatedSring() {
+ $this->assertResult(
+ $var = 'He said<br />perhaps<br />we should <b>nuke</b> them.',
+ "<p>$var</p>"
+ );
+ }
+
+ function testWrapAndRemoveInvalidTagsComplex() {
+ $this->assertResult(
+ '<foo>Bar</foo><bas /><b>People</b>Conniving.'. '<p>Fools!</p>',
+ '<p>Bar'. '<b>People</b>Conniving.</p><p>Fools!</p>'
+ );
+ }
+
+ function testAlternateWrapper() {
+ $this->config->set('HTML.BlockWrapper', 'div');
+ $this->assertResult('Needs wrap', '<div>Needs wrap</div>');
+
+ }
+
+ function testError() {
+ $this->expectError('Cannot use non-block element as block wrapper');
+ $this->obj = new HTMLPurifier_ChildDef_StrictBlockquote('div | p');
+ $this->config->set('HTML.BlockWrapper', 'dav');
+ $this->config->set('Cache.DefinitionImpl', null);
+ $this->assertResult('Needs wrap', '<p>Needs wrap</p>');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/TableTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/TableTest.php
new file mode 100644
index 000000000..2f72d187a
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/TableTest.php
@@ -0,0 +1,75 @@
+<?php
+
+// we're using empty tags to compact the tests: under real circumstances
+// there would be contents in them
+
+class HTMLPurifier_ChildDef_TableTest extends HTMLPurifier_ChildDefHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_ChildDef_Table();
+ }
+
+ function testEmptyInput() {
+ $this->assertResult('', false);
+ }
+
+ function testSingleRow() {
+ $this->assertResult('<tr />');
+ }
+
+ function testComplexContents() {
+ $this->assertResult('<caption /><col /><thead /><tfoot /><tbody>'.
+ '<tr><td>asdf</td></tr></tbody>');
+ $this->assertResult('<col /><col /><col /><tr />');
+ }
+
+ function testReorderContents() {
+ $this->assertResult(
+ '<col /><colgroup /><tbody /><tfoot /><thead /><tr>1</tr><caption /><tr />',
+ '<caption /><col /><colgroup /><thead /><tfoot /><tbody /><tbody><tr>1</tr><tr /></tbody>');
+ }
+
+ function testXhtml11Illegal() {
+ $this->assertResult(
+ '<thead><tr><th>a</th></tr></thead><tr><td>a</td></tr>',
+ '<thead><tr><th>a</th></tr></thead><tbody><tr><td>a</td></tr></tbody>'
+ );
+ }
+
+ function testTrOverflowAndClose() {
+ $this->assertResult(
+ '<tr><td>a</td></tr><tr><td>b</td></tr><tbody><tr><td>c</td></tr></tbody><tr><td>d</td></tr>',
+ '<tbody><tr><td>a</td></tr><tr><td>b</td></tr></tbody><tbody><tr><td>c</td></tr></tbody><tbody><tr><td>d</td></tr></tbody>'
+ );
+ }
+
+ function testDuplicateProcessing() {
+ $this->assertResult(
+ '<caption>1</caption><caption /><tbody /><tbody /><tfoot>1</tfoot><tfoot />',
+ '<caption>1</caption><tfoot>1</tfoot><tbody /><tbody /><tbody />'
+ );
+ }
+
+ function testRemoveText() {
+ $this->assertResult('foo', false);
+ }
+
+ function testStickyWhitespaceOnTr() {
+ $this->config->set('Output.Newline', "\n");
+ $this->assertResult("\n <tr />\n <tr />\n ");
+ }
+
+ function testStickyWhitespaceOnTSection() {
+ $this->config->set('Output.Newline', "\n");
+ $this->assertResult(
+ "\n\t<tbody />\n\t\t<tfoot />\n\t\t\t",
+ "\n\t\t<tfoot />\n\t<tbody />\n\t\t\t"
+ );
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ChildDefHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/ChildDefHarness.php
new file mode 100644
index 000000000..4b4d2f5b6
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ChildDefHarness.php
@@ -0,0 +1,16 @@
+<?php
+
+class HTMLPurifier_ChildDefHarness extends HTMLPurifier_ComplexHarness
+{
+
+ public function setUp() {
+ parent::setUp();
+ $this->obj = null;
+ $this->func = 'validateChildren';
+ $this->to_tokens = true;
+ $this->to_html = true;
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ComplexHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/ComplexHarness.php
new file mode 100644
index 000000000..8e806c63c
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ComplexHarness.php
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * General-purpose test-harness that makes testing functions that require
+ * configuration and context objects easier when those two parameters are
+ * meaningless. See HTMLPurifier_ChildDefTest for a good example of usage.
+ */
+class HTMLPurifier_ComplexHarness extends HTMLPurifier_Harness
+{
+
+ /**
+ * Instance of the object that will execute the method
+ */
+ protected $obj;
+
+ /**
+ * Name of the function to be executed
+ */
+ protected $func;
+
+ /**
+ * Whether or not the method deals in tokens. If set to true, assertResult()
+ * will transparently convert HTML to and back from tokens.
+ */
+ protected $to_tokens = false;
+
+ /**
+ * Whether or not to convert tokens back into HTML before performing
+ * equality check, has no effect on bools.
+ */
+ protected $to_html = false;
+
+ /**
+ * Instance of an HTMLPurifier_Lexer implementation.
+ */
+ protected $lexer;
+
+ public function __construct() {
+ $this->lexer = new HTMLPurifier_Lexer_DirectLex();
+ parent::__construct();
+ }
+
+ /**
+ * Asserts a specific result from a one parameter + config/context function
+ * @param $input Input parameter
+ * @param $expect Expectation
+ * @param $config Configuration array in form of Ns.Directive => Value.
+ * Has no effect if $this->config is set.
+ * @param $context_array Context array in form of Key => Value or an actual
+ * context object.
+ */
+ protected function assertResult($input, $expect = true) {
+
+ if ($this->to_tokens && is_string($input)) {
+ // $func may cause $input to change, so "clone" another copy
+ // to sacrifice
+ $input = $this->tokenize($temp = $input);
+ $input_c = $this->tokenize($temp);
+ } else {
+ $input_c = $input;
+ }
+
+ // call the function
+ $func = $this->func;
+ $result = $this->obj->$func($input_c, $this->config, $this->context);
+
+ // test a bool result
+ if (is_bool($result)) {
+ $this->assertIdentical($expect, $result);
+ return;
+ } elseif (is_bool($expect)) {
+ $expect = $input;
+ }
+
+ if ($this->to_html) {
+ $result = $this->generate($result);
+ if (is_array($expect)) {
+ $expect = $this->generate($expect);
+ }
+ }
+ $this->assertIdentical($expect, $result);
+
+ if ($expect !== $result) {
+ echo '<pre>' . var_dump($result) . '</pre>';
+ }
+
+ }
+
+ /**
+ * Tokenize HTML into tokens, uses member variables for common variables
+ */
+ protected function tokenize($html) {
+ return $this->lexer->tokenizeHTML($html, $this->config, $this->context);
+ }
+
+ /**
+ * Generate textual HTML from tokens
+ */
+ protected function generate($tokens) {
+ $generator = new HTMLPurifier_Generator($this->config, $this->context);
+ return $generator->generateFromTokens($tokens);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/InterchangeTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/InterchangeTest.php
new file mode 100644
index 000000000..0d539ea2c
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/InterchangeTest.php
@@ -0,0 +1,21 @@
+<?php
+
+class HTMLPurifier_ConfigSchema_InterchangeTest extends UnitTestCase
+{
+
+ protected $interchange;
+
+ public function setup() {
+ $this->interchange = new HTMLPurifier_ConfigSchema_Interchange();
+ }
+
+ function testAddDirective() {
+ $v = new HTMLPurifier_ConfigSchema_Interchange_Directive();
+ $v->id = new HTMLPurifier_ConfigSchema_Interchange_Id('Namespace.Directive');
+ $this->interchange->addDirective($v);
+ $this->assertIdentical($v, $this->interchange->directives['Namespace.Directive']);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/aliasesAliasCollision.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/aliasesAliasCollision.vtest
new file mode 100644
index 000000000..ed3857c40
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/aliasesAliasCollision.vtest
@@ -0,0 +1,13 @@
+ERROR: Alias 'Ns.BothWantThisName' in aliases in directive 'Ns.Dir2' collides with alias for directive 'Ns.Dir'
+----
+Ns.Dir
+DESCRIPTION: Directive
+TYPE: int
+DEFAULT: 3
+ALIASES: Ns.BothWantThisName
+----
+Ns.Dir2
+DESCRIPTION: Directive
+TYPE: string
+DEFAULT: 'a'
+ALIASES: Ns.BothWantThisName
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/aliasesDirectiveCollision.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/aliasesDirectiveCollision.vtest
new file mode 100644
index 000000000..582a481a4
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/aliasesDirectiveCollision.vtest
@@ -0,0 +1,12 @@
+ERROR: Alias 'Ns.Innocent' in aliases in directive 'Ns.Dir' collides with another directive
+----
+Ns.Innocent
+DESCRIPTION: Innocent directive
+TYPE: int
+DEFAULT: 3
+----
+Ns.Dir
+DESCRIPTION: Directive
+TYPE: string
+DEFAULT: 'a'
+ALIASES: Ns.Innocent
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/allowedIsString.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/allowedIsString.vtest
new file mode 100644
index 000000000..9ecc5f930
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/allowedIsString.vtest
@@ -0,0 +1,7 @@
+ERROR: Value 3 in allowed in directive 'Ns.Dir' must be a string
+----
+ID: Ns.Dir
+TYPE: string
+DESCRIPTION: Description
+DEFAULT: 'asdf'
+ALLOWED: 'asdf', 3
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/allowedNotEmpty.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/allowedNotEmpty.vtest
new file mode 100644
index 000000000..1e08fab0d
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/allowedNotEmpty.vtest
@@ -0,0 +1,7 @@
+ERROR: Allowed in directive 'Ns.Dir' must not be empty
+----
+ID: Ns.Dir
+TYPE: string
+DESCRIPTION: Description
+DEFAULT: 'asdf'
+ALLOWED:
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultIsAllowed.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultIsAllowed.vtest
new file mode 100644
index 000000000..ef1944707
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultIsAllowed.vtest
@@ -0,0 +1,7 @@
+ERROR: Default in directive 'Ns.Dir' must be an allowed value
+----
+Ns.Dir
+DESCRIPTION: Directive
+TYPE: string
+DEFAULT: 'a'
+ALLOWED: 'b'
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultNullWithAllowed.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultNullWithAllowed.vtest
new file mode 100644
index 000000000..7ba3c85c0
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultNullWithAllowed.vtest
@@ -0,0 +1,5 @@
+Ns.Dir
+DESCRIPTION: Directive
+TYPE: string/null
+DEFAULT: null
+ALLOWED: 'a'
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultType.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultType.vtest
new file mode 100644
index 000000000..79ea043ac
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultType.vtest
@@ -0,0 +1,6 @@
+ERROR: Expected type string, got integer in DEFAULT in directive hash 'Ns.Dir'
+----
+Ns.Dir
+DESCRIPTION: Directive
+TYPE: string
+DEFAULT: 0
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/descriptionNotEmpty.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/descriptionNotEmpty.vtest
new file mode 100644
index 000000000..d09ddc77b
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/descriptionNotEmpty.vtest
@@ -0,0 +1,5 @@
+ERROR: Description in directive 'Ns.Dir' must not be empty
+----
+Ns.Dir
+TYPE: int
+DEFAULT: 0
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/ignoreNamespace.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/ignoreNamespace.vtest
new file mode 100644
index 000000000..3fc28d3a4
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/ignoreNamespace.vtest
@@ -0,0 +1,3 @@
+Ns
+DESCRIPTION: Namespace
+
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeDefined.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeDefined.vtest
new file mode 100644
index 000000000..4bb8abeb2
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeDefined.vtest
@@ -0,0 +1,5 @@
+ERROR: TYPE in directive hash 'Ns.Dir' not defined
+----
+Ns.Dir
+DESCRIPTION: Notice that TYPE is missing
+DEFAULT: 0
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeExists.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeExists.vtest
new file mode 100644
index 000000000..f9851f536
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeExists.vtest
@@ -0,0 +1,6 @@
+ERROR: Invalid type 'foobar' in DEFAULT in directive hash 'Ns.Dir'
+----
+Ns.Dir
+DESCRIPTION: Directive
+TYPE: foobar
+DEFAULT: 0
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeWithAllowedIsStringType.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeWithAllowedIsStringType.vtest
new file mode 100644
index 000000000..bf4e2b21b
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeWithAllowedIsStringType.vtest
@@ -0,0 +1,7 @@
+ERROR: Type in directive 'Ns.Dir' must be a string type when used with allowed or value aliases
+----
+Ns.Dir
+DESCRIPTION: Directive
+TYPE: int
+DEFAULT: 3
+ALLOWED: 1, 2, 3
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeWithValueAliasesIsStringType.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeWithValueAliasesIsStringType.vtest
new file mode 100644
index 000000000..59cf4333a
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeWithValueAliasesIsStringType.vtest
@@ -0,0 +1,7 @@
+ERROR: Type in directive 'Ns.Dir' must be a string type when used with allowed or value aliases
+----
+Ns.Dir
+DESCRIPTION: Directive
+TYPE: int
+DEFAULT: 3
+VALUE-ALIASES: 2 => 3
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/unique.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/unique.vtest
new file mode 100644
index 000000000..c83649e8d
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/unique.vtest
@@ -0,0 +1,11 @@
+ERROR: Cannot redefine directive 'Ns.Dir'
+----
+ID: Ns.Dir
+DESCRIPTION: Version 1
+TYPE: int
+DEFAULT: 0
+----
+ID: Ns.Dir
+DESCRIPTION: Version 2
+TYPE: int
+DEFAULT: 0
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesAliasIsString.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesAliasIsString.vtest
new file mode 100644
index 000000000..b9f28eee1
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesAliasIsString.vtest
@@ -0,0 +1,7 @@
+ERROR: Alias 3 in valueAliases in directive 'Ns.Dir' must be a string
+----
+Ns.Dir
+DESCRIPTION: Directive
+TYPE: string
+DEFAULT: 'a'
+VALUE-ALIASES: 3 => 'a'
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesAliasNotAllowed.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesAliasNotAllowed.vtest
new file mode 100644
index 000000000..ef9c14883
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesAliasNotAllowed.vtest
@@ -0,0 +1,8 @@
+ERROR: Alias 'b' in valueAliases in directive 'Ns.Dir' must not be an allowed value
+----
+Ns.Dir
+DESCRIPTION: Directive
+TYPE: string
+DEFAULT: 'a'
+ALLOWED: 'a', 'b', 'c'
+VALUE-ALIASES: 'b' => 'c'
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesNotAliasSelf.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesNotAliasSelf.vtest
new file mode 100644
index 000000000..4c417fd5f
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesNotAliasSelf.vtest
@@ -0,0 +1,7 @@
+ERROR: Alias 'bar' in valueAliases in directive 'Ns.Dir' must not be an alias to itself
+----
+Ns.Dir
+DESCRIPTION: Directive
+TYPE: string
+DEFAULT: 'foo'
+VALUE-ALIASES: 'bar' => 'bar'
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesRealAllowed.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesRealAllowed.vtest
new file mode 100644
index 000000000..89502d34a
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesRealAllowed.vtest
@@ -0,0 +1,8 @@
+ERROR: Alias 'c' in valueAliases in directive 'Ns.Dir' must be an alias to an allowed value
+----
+Ns.Dir
+DESCRIPTION: Directive
+TYPE: string
+DEFAULT: 'a'
+ALLOWED: 'a', 'b'
+VALUE-ALIASES: 'c' => 'd'
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesRealIsString.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesRealIsString.vtest
new file mode 100644
index 000000000..92ec197dc
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesRealIsString.vtest
@@ -0,0 +1,7 @@
+ERROR: Alias target 3 from alias 'b' in valueAliases in directive 'Ns.Dir' must be a string
+----
+Ns.Dir
+DESCRIPTION: Directive
+TYPE: string
+DEFAULT: 'a'
+VALUE-ALIASES: 'b' => 3
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorAtomTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorAtomTest.php
new file mode 100644
index 000000000..38625136e
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorAtomTest.php
@@ -0,0 +1,92 @@
+<?php
+
+class HTMLPurifier_ConfigSchema_ValidatorAtomTest extends UnitTestCase
+{
+
+ protected function expectValidationException($msg) {
+ $this->expectException(new HTMLPurifier_ConfigSchema_Exception($msg));
+ }
+
+ protected function makeAtom($value) {
+ $obj = new stdClass();
+ $obj->property = $value;
+ // Note that 'property' and 'context' are magic wildcard values
+ return new HTMLPurifier_ConfigSchema_ValidatorAtom('context', $obj, 'property');
+ }
+
+ function testAssertIsString() {
+ $this->makeAtom('foo')->assertIsString();
+ }
+
+ function testAssertIsStringFail() {
+ $this->expectValidationException("Property in context must be a string");
+ $this->makeAtom(3)->assertIsString();
+ }
+
+ function testAssertNotNull() {
+ $this->makeAtom('foo')->assertNotNull();
+ }
+
+ function testAssertNotNullFail() {
+ $this->expectValidationException("Property in context must not be null");
+ $this->makeAtom(null)->assertNotNull();
+ }
+
+ function testAssertAlnum() {
+ $this->makeAtom('foo2')->assertAlnum();
+ }
+
+ function testAssertAlnumFail() {
+ $this->expectValidationException("Property in context must be alphanumeric");
+ $this->makeAtom('%a')->assertAlnum();
+ }
+
+ function testAssertAlnumFailIsString() {
+ $this->expectValidationException("Property in context must be a string");
+ $this->makeAtom(3)->assertAlnum();
+ }
+
+ function testAssertNotEmpty() {
+ $this->makeAtom('foo')->assertNotEmpty();
+ }
+
+ function testAssertNotEmptyFail() {
+ $this->expectValidationException("Property in context must not be empty");
+ $this->makeAtom('')->assertNotEmpty();
+ }
+
+ function testAssertIsBool() {
+ $this->makeAtom(false)->assertIsBool();
+ }
+
+ function testAssertIsBoolFail() {
+ $this->expectValidationException("Property in context must be a boolean");
+ $this->makeAtom('0')->assertIsBool();
+ }
+
+ function testAssertIsArray() {
+ $this->makeAtom(array())->assertIsArray();
+ }
+
+ function testAssertIsArrayFail() {
+ $this->expectValidationException("Property in context must be an array");
+ $this->makeAtom('asdf')->assertIsArray();
+ }
+
+
+ function testAssertIsLookup() {
+ $this->makeAtom(array('foo' => true))->assertIsLookup();
+ }
+
+ function testAssertIsLookupFail() {
+ $this->expectValidationException("Property in context must be a lookup array");
+ $this->makeAtom(array('foo' => 4))->assertIsLookup();
+ }
+
+ function testAssertIsLookupFailIsArray() {
+ $this->expectValidationException("Property in context must be an array");
+ $this->makeAtom('asdf')->assertIsLookup();
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorTest.php
new file mode 100644
index 000000000..9cbf36e2d
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorTest.php
@@ -0,0 +1,101 @@
+<?php
+
+/**
+ * Special test-case for cases that can't be tested using
+ * HTMLPurifier_ConfigSchema_ValidatorTestCase.
+ */
+class HTMLPurifier_ConfigSchema_ValidatorTest extends UnitTestCase
+{
+ public $validator, $interchange;
+
+ public function setup() {
+ $this->validator = new HTMLPurifier_ConfigSchema_Validator();
+ $this->interchange = new HTMLPurifier_ConfigSchema_Interchange();
+ }
+
+ function testDirectiveIntegrityViolation() {
+ $d = $this->makeDirective('Ns.Dir');
+ $d->id = new HTMLPurifier_ConfigSchema_Interchange_Id('Ns.Dir2');
+ $this->expectValidationException("Integrity violation: key 'Ns.Dir' does not match internal id 'Ns.Dir2'");
+ $this->validator->validate($this->interchange);
+ }
+
+ function testDirectiveTypeNotEmpty() {
+ $d = $this->makeDirective('Ns.Dir');
+ $d->default = 0;
+ $d->description = 'Description';
+
+ $this->expectValidationException("Type in directive 'Ns.Dir' must not be empty");
+ $this->validator->validate($this->interchange);
+ }
+
+ function testDirectiveDefaultInvalid() {
+ $d = $this->makeDirective('Ns.Dir');
+ $d->default = 'asdf';
+ $d->type = 'int';
+ $d->description = 'Description';
+
+ $this->expectValidationException("Default in directive 'Ns.Dir' had error: Expected type int, got string");
+ $this->validator->validate($this->interchange);
+ }
+
+ function testDirectiveIdIsString() {
+ $d = $this->makeDirective(3);
+ $d->default = 0;
+ $d->type = 'int';
+ $d->description = 'Description';
+
+ $this->expectValidationException("Key in id '3' in directive '3' must be a string");
+ $this->validator->validate($this->interchange);
+ }
+
+ function testDirectiveTypeAllowsNullIsBool() {
+ $d = $this->makeDirective('Ns.Dir');
+ $d->default = 0;
+ $d->type = 'int';
+ $d->description = 'Description';
+ $d->typeAllowsNull = 'yes';
+
+ $this->expectValidationException("TypeAllowsNull in directive 'Ns.Dir' must be a boolean");
+ $this->validator->validate($this->interchange);
+ }
+
+ function testDirectiveValueAliasesIsArray() {
+ $d = $this->makeDirective('Ns.Dir');
+ $d->default = 'a';
+ $d->type = 'string';
+ $d->description = 'Description';
+ $d->valueAliases = 2;
+
+ $this->expectValidationException("ValueAliases in directive 'Ns.Dir' must be an array");
+ $this->validator->validate($this->interchange);
+ }
+
+ function testDirectiveAllowedIsLookup() {
+ $d = $this->makeDirective('Ns.Dir');
+ $d->default = 'foo';
+ $d->type = 'string';
+ $d->description = 'Description';
+ $d->allowed = array('foo' => 1);
+
+ $this->expectValidationException("Allowed in directive 'Ns.Dir' must be a lookup array");
+ $this->validator->validate($this->interchange);
+ }
+
+ // helper functions
+
+
+ protected function makeDirective($key) {
+ $directive = new HTMLPurifier_ConfigSchema_Interchange_Directive();
+ $directive->id = new HTMLPurifier_ConfigSchema_Interchange_Id($key);
+ $this->interchange->addDirective($directive);
+ return $directive;
+ }
+
+ protected function expectValidationException($msg) {
+ $this->expectException(new HTMLPurifier_ConfigSchema_Exception($msg));
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorTestCase.php b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorTestCase.php
new file mode 100644
index 000000000..87fa14d0d
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorTestCase.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * Controller for validator test-cases.
+ */
+class HTMLPurifier_ConfigSchema_ValidatorTestCase extends UnitTestCase
+{
+
+ protected $_path, $_parser, $_builder;
+ public $validator;
+
+ public function __construct($path) {
+ $this->_path = $path;
+ $this->_parser = new HTMLPurifier_StringHashParser();
+ $this->_builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder();
+ parent::__construct($path);
+ }
+
+ public function setup() {
+ $this->validator = new HTMLPurifier_ConfigSchema_Validator();
+ }
+
+ function testValidator() {
+ $hashes = $this->_parser->parseMultiFile($this->_path);
+ $interchange = new HTMLPurifier_ConfigSchema_Interchange();
+ $error = null;
+ foreach ($hashes as $hash) {
+ if (!isset($hash['ID'])) {
+ if (isset($hash['ERROR'])) {
+ $this->expectException(
+ new HTMLPurifier_ConfigSchema_Exception($hash['ERROR'])
+ );
+ }
+ continue;
+ }
+ $this->_builder->build($interchange, new HTMLPurifier_StringHash($hash));
+ }
+ $this->validator->validate($interchange);
+ $this->pass();
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchemaTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchemaTest.php
new file mode 100644
index 000000000..dc3bf99eb
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchemaTest.php
@@ -0,0 +1,99 @@
+<?php
+
+class HTMLPurifier_ConfigSchemaTest extends HTMLPurifier_Harness
+{
+
+ protected $schema;
+
+ public function setup() {
+ $this->schema = new HTMLPurifier_ConfigSchema();
+ }
+
+ function test_define() {
+ $this->schema->add('Car.Seats', 5, 'int', false);
+
+ $this->assertIdentical($this->schema->defaults['Car.Seats'], 5);
+ $this->assertIdentical($this->schema->info['Car.Seats']->type, HTMLPurifier_VarParser::INT);
+
+ $this->schema->add('Car.Age', null, 'int', true);
+
+ $this->assertIdentical($this->schema->defaults['Car.Age'], null);
+ $this->assertIdentical($this->schema->info['Car.Age']->type, HTMLPurifier_VarParser::INT);
+
+ }
+
+ function test_defineAllowedValues() {
+ $this->schema->add('QuantumNumber.Spin', 0.5, 'float', false);
+ $this->schema->add('QuantumNumber.Current', 's', 'string', false);
+ $this->schema->add('QuantumNumber.Difficulty', null, 'string', true);
+
+ $this->schema->addAllowedValues( // okay, since default is null
+ 'QuantumNumber.Difficulty', array('easy' => true, 'medium' => true, 'hard' => true)
+ );
+
+ $this->assertIdentical($this->schema->defaults['QuantumNumber.Difficulty'], null);
+ $this->assertIdentical($this->schema->info['QuantumNumber.Difficulty']->type, HTMLPurifier_VarParser::STRING);
+ $this->assertIdentical($this->schema->info['QuantumNumber.Difficulty']->allow_null, true);
+ $this->assertIdentical($this->schema->info['QuantumNumber.Difficulty']->allowed,
+ array(
+ 'easy' => true,
+ 'medium' => true,
+ 'hard' => true
+ )
+ );
+
+ }
+
+ function test_defineValueAliases() {
+ $this->schema->add('Abbrev.HTH', 'Happy to Help', 'string', false);
+ $this->schema->addAllowedValues(
+ 'Abbrev.HTH', array(
+ 'Happy to Help' => true,
+ 'Hope that Helps' => true,
+ 'HAIL THE HAND!' => true,
+ )
+ );
+ $this->schema->addValueAliases(
+ 'Abbrev.HTH', array(
+ 'happy' => 'Happy to Help',
+ 'hope' => 'Hope that Helps'
+ )
+ );
+ $this->schema->addValueAliases( // delayed addition
+ 'Abbrev.HTH', array(
+ 'hail' => 'HAIL THE HAND!'
+ )
+ );
+
+ $this->assertIdentical($this->schema->defaults['Abbrev.HTH'], 'Happy to Help');
+ $this->assertIdentical($this->schema->info['Abbrev.HTH']->type, HTMLPurifier_VarParser::STRING);
+ $this->assertIdentical($this->schema->info['Abbrev.HTH']->allowed,
+ array(
+ 'Happy to Help' => true,
+ 'Hope that Helps' => true,
+ 'HAIL THE HAND!' => true
+ )
+ );
+ $this->assertIdentical($this->schema->info['Abbrev.HTH']->aliases,
+ array(
+ 'happy' => 'Happy to Help',
+ 'hope' => 'Hope that Helps',
+ 'hail' => 'HAIL THE HAND!'
+ )
+ );
+
+ }
+
+ function testAlias() {
+ $this->schema->add('Home.Rug', 3, 'int', false);
+ $this->schema->addAlias('Home.Carpet', 'Home.Rug');
+
+ $this->assertTrue(!isset($this->schema->defaults['Home.Carpet']));
+ $this->assertIdentical($this->schema->info['Home.Carpet']->key, 'Home.Rug');
+ $this->assertIdentical($this->schema->info['Home.Carpet']->isAlias, true);
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-create.ini b/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-create.ini
new file mode 100644
index 000000000..31412f99a
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-create.ini
@@ -0,0 +1,4 @@
+[Cake]
+Sprinkles = 42
+
+; vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-finalize.ini b/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-finalize.ini
new file mode 100644
index 000000000..3ab185fad
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-finalize.ini
@@ -0,0 +1,4 @@
+[Poem]
+Meter = alexandrine
+
+; vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-loadIni.ini b/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-loadIni.ini
new file mode 100644
index 000000000..cfa5a907f
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-loadIni.ini
@@ -0,0 +1,6 @@
+[Shortcut]
+Copy = q
+Cut = t
+Paste = p
+
+; vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest.php
new file mode 100644
index 000000000..3d36fc057
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest.php
@@ -0,0 +1,557 @@
+<?php
+
+class HTMLPurifier_ConfigTest extends HTMLPurifier_Harness
+{
+
+ protected $schema;
+ protected $oldFactory;
+
+ public function setUp() {
+ // set up a dummy schema object for testing
+ $this->schema = new HTMLPurifier_ConfigSchema();
+ }
+
+ // test functionality based on ConfigSchema
+
+ function testNormal() {
+ $this->schema->add('Element.Abbr', 'H', 'string', false);
+ $this->schema->add('Element.Name', 'hydrogen', 'istring', false);
+ $this->schema->add('Element.Number', 1, 'int', false);
+ $this->schema->add('Element.Mass', 1.00794, 'float', false);
+ $this->schema->add('Element.Radioactive', false, 'bool', false);
+ $this->schema->add('Element.Isotopes', array(1 => true, 2 => true, 3 => true), 'lookup', false);
+ $this->schema->add('Element.Traits', array('nonmetallic', 'odorless', 'flammable'), 'list', false);
+ $this->schema->add('Element.IsotopeNames', array(1 => 'protium', 2 => 'deuterium', 3 => 'tritium'), 'hash', false);
+ $this->schema->add('Element.Object', new stdClass(), 'mixed', false);
+
+ $config = new HTMLPurifier_Config($this->schema);
+ $config->autoFinalize = false;
+ $config->chatty = false;
+
+ // test default value retrieval
+ $this->assertIdentical($config->get('Element.Abbr'), 'H');
+ $this->assertIdentical($config->get('Element.Name'), 'hydrogen');
+ $this->assertIdentical($config->get('Element.Number'), 1);
+ $this->assertIdentical($config->get('Element.Mass'), 1.00794);
+ $this->assertIdentical($config->get('Element.Radioactive'), false);
+ $this->assertIdentical($config->get('Element.Isotopes'), array(1 => true, 2 => true, 3 => true));
+ $this->assertIdentical($config->get('Element.Traits'), array('nonmetallic', 'odorless', 'flammable'));
+ $this->assertIdentical($config->get('Element.IsotopeNames'), array(1 => 'protium', 2 => 'deuterium', 3 => 'tritium'));
+ $this->assertIdentical($config->get('Element.Object'), new stdClass());
+
+ // test setting values
+ $config->set('Element.Abbr', 'Pu');
+ $config->set('Element.Name', 'PLUTONIUM'); // test decaps
+ $config->set('Element.Number', '94'); // test parsing
+ $config->set('Element.Mass', '244.'); // test parsing
+ $config->set('Element.Radioactive', true);
+ $config->set('Element.Isotopes', array(238, 239)); // test inversion
+ $config->set('Element.Traits', 'nuclear, heavy, actinide'); // test parsing
+ $config->set('Element.IsotopeNames', array(238 => 'Plutonium-238', 239 => 'Plutonium-239'));
+ $config->set('Element.Object', false); // unmodeled
+
+ $this->expectError('Cannot set undefined directive Element.Metal to value');
+ $config->set('Element.Metal', true);
+
+ $this->expectError('Value for Element.Radioactive is of invalid type, should be bool');
+ $config->set('Element.Radioactive', 'very');
+
+ // test value retrieval
+ $this->assertIdentical($config->get('Element.Abbr'), 'Pu');
+ $this->assertIdentical($config->get('Element.Name'), 'plutonium');
+ $this->assertIdentical($config->get('Element.Number'), 94);
+ $this->assertIdentical($config->get('Element.Mass'), 244.);
+ $this->assertIdentical($config->get('Element.Radioactive'), true);
+ $this->assertIdentical($config->get('Element.Isotopes'), array(238 => true, 239 => true));
+ $this->assertIdentical($config->get('Element.Traits'), array('nuclear', 'heavy', 'actinide'));
+ $this->assertIdentical($config->get('Element.IsotopeNames'), array(238 => 'Plutonium-238', 239 => 'Plutonium-239'));
+ $this->assertIdentical($config->get('Element.Object'), false);
+
+ $this->expectError('Cannot retrieve value of undefined directive Element.Metal');
+ $config->get('Element.Metal');
+
+ }
+
+ function testEnumerated() {
+
+ // case sensitive
+ $this->schema->add('Instrument.Manufacturer', 'Yamaha', 'string', false);
+ $this->schema->addAllowedValues('Instrument.Manufacturer', array(
+ 'Yamaha' => true, 'Conn-Selmer' => true, 'Vandoren' => true,
+ 'Laubin' => true, 'Buffet' => true, 'other' => true));
+ $this->schema->addValueAliases('Instrument.Manufacturer', array(
+ 'Selmer' => 'Conn-Selmer'));
+
+ // case insensitive
+ $this->schema->add('Instrument.Family', 'woodwind', 'istring', false);
+ $this->schema->addAllowedValues('Instrument.Family', array(
+ 'brass' => true, 'woodwind' => true, 'percussion' => true,
+ 'string' => true, 'keyboard' => true, 'electronic' => true));
+ $this->schema->addValueAliases('Instrument.Family', array(
+ 'synth' => 'electronic'));
+
+ $config = new HTMLPurifier_Config($this->schema);
+ $config->autoFinalize = false;
+ $config->chatty = false;
+
+ // case sensitive
+
+ $config->set('Instrument.Manufacturer', 'Vandoren');
+ $this->assertIdentical($config->get('Instrument.Manufacturer'), 'Vandoren');
+
+ $config->set('Instrument.Manufacturer', 'Selmer');
+ $this->assertIdentical($config->get('Instrument.Manufacturer'), 'Conn-Selmer');
+
+ $this->expectError('Value not supported, valid values are: Yamaha, Conn-Selmer, Vandoren, Laubin, Buffet, other');
+ $config->set('Instrument.Manufacturer', 'buffet');
+
+ // case insensitive
+
+ $config->set('Instrument.Family', 'brass');
+ $this->assertIdentical($config->get('Instrument.Family'), 'brass');
+
+ $config->set('Instrument.Family', 'PERCUSSION');
+ $this->assertIdentical($config->get('Instrument.Family'), 'percussion');
+
+ $config->set('Instrument.Family', 'synth');
+ $this->assertIdentical($config->get('Instrument.Family'), 'electronic');
+
+ $config->set('Instrument.Family', 'Synth');
+ $this->assertIdentical($config->get('Instrument.Family'), 'electronic');
+
+ }
+
+ function testNull() {
+
+ $this->schema->add('ReportCard.English', null, 'string', true);
+ $this->schema->add('ReportCard.Absences', 0, 'int', false);
+
+ $config = new HTMLPurifier_Config($this->schema);
+ $config->autoFinalize = false;
+ $config->chatty = false;
+
+ $config->set('ReportCard.English', 'B-');
+ $this->assertIdentical($config->get('ReportCard.English'), 'B-');
+
+ $config->set('ReportCard.English', null); // not yet graded
+ $this->assertIdentical($config->get('ReportCard.English'), null);
+
+ // error
+ $this->expectError('Value for ReportCard.Absences is of invalid type, should be int');
+ $config->set('ReportCard.Absences', null);
+
+ }
+
+ function testAliases() {
+
+ $this->schema->add('Home.Rug', 3, 'int', false);
+ $this->schema->addAlias('Home.Carpet', 'Home.Rug');
+
+ $config = new HTMLPurifier_Config($this->schema);
+ $config->autoFinalize = false;
+ $config->chatty = false;
+
+ $this->assertIdentical($config->get('Home.Rug'), 3);
+
+ $this->expectError('Cannot get value from aliased directive, use real name Home.Rug');
+ $config->get('Home.Carpet');
+
+ $this->expectError('Home.Carpet is an alias, preferred directive name is Home.Rug');
+ $config->set('Home.Carpet', 999);
+ $this->assertIdentical($config->get('Home.Rug'), 999);
+
+ }
+
+ // test functionality based on method
+
+ function test_getBatch() {
+
+ $this->schema->add('Variables.TangentialAcceleration', 'a_tan', 'string', false);
+ $this->schema->add('Variables.AngularAcceleration', 'alpha', 'string', false);
+
+ $config = new HTMLPurifier_Config($this->schema);
+ $config->autoFinalize = false;
+ $config->chatty = false;
+
+ // grab a namespace
+ $this->assertIdentical(
+ $config->getBatch('Variables'),
+ array(
+ 'TangentialAcceleration' => 'a_tan',
+ 'AngularAcceleration' => 'alpha'
+ )
+ );
+
+ // grab a non-existant namespace
+ $this->expectError('Cannot retrieve undefined namespace Constants');
+ $config->getBatch('Constants');
+
+ }
+
+ function test_loadIni() {
+
+ $this->schema->add('Shortcut.Copy', 'c', 'istring', false);
+ $this->schema->add('Shortcut.Paste', 'v', 'istring', false);
+ $this->schema->add('Shortcut.Cut', 'x', 'istring', false);
+
+ $config = new HTMLPurifier_Config($this->schema);
+ $config->autoFinalize = false;
+
+ $config->loadIni(dirname(__FILE__) . '/ConfigTest-loadIni.ini');
+
+ $this->assertIdentical($config->get('Shortcut.Copy'), 'q');
+ $this->assertIdentical($config->get('Shortcut.Paste'), 'p');
+ $this->assertIdentical($config->get('Shortcut.Cut'), 't');
+
+ }
+
+ function test_getHTMLDefinition() {
+
+ // we actually want to use the old copy, because the definition
+ // generation routines have dependencies on configuration values
+
+ $config = HTMLPurifier_Config::createDefault();
+ $config->set('HTML.Doctype', 'XHTML 1.0 Strict');
+ $config->autoFinalize = false;
+
+ $def = $config->getCSSDefinition();
+ $this->assertIsA($def, 'HTMLPurifier_CSSDefinition');
+
+ $def = $config->getHTMLDefinition();
+ $def2 = $config->getHTMLDefinition();
+ $this->assertIsA($def, 'HTMLPurifier_HTMLDefinition');
+ $this->assertTrue($def === $def2);
+ $this->assertTrue($def->setup);
+
+ $old_def = clone $def2;
+
+ $config->set('HTML.Doctype', 'HTML 4.01 Transitional');
+ $def = $config->getHTMLDefinition();
+ $this->assertIsA($def, 'HTMLPurifier_HTMLDefinition');
+ $this->assertTrue($def !== $old_def);
+ $this->assertTrue($def->setup);
+
+ }
+
+ function test_getHTMLDefinition_deprecatedRawError() {
+ $config = HTMLPurifier_Config::createDefault();
+ $config->chatty = false;
+ // test deprecated retrieval of raw definition
+ $config->set('HTML.DefinitionID', 'HTMLPurifier_ConfigTest->test_getHTMLDefinition()');
+ $config->set('HTML.DefinitionRev', 3);
+ $this->expectError("Useless DefinitionID declaration");
+ $def = $config->getHTMLDefinition(true);
+ $this->assertEqual(false, $def->setup);
+
+ // auto initialization
+ $config->getHTMLDefinition();
+ $this->assertTrue($def->setup);
+ }
+
+ function test_getHTMLDefinition_optimizedRawError() {
+ $this->expectException(new HTMLPurifier_Exception("Cannot set optimized = true when raw = false"));
+ $config = HTMLPurifier_Config::createDefault();
+ $config->getHTMLDefinition(false, true);
+ }
+
+ function test_getHTMLDefinition_rawAfterSetupError() {
+ $this->expectException(new HTMLPurifier_Exception("Cannot retrieve raw definition after it has already been setup"));
+ $config = HTMLPurifier_Config::createDefault();
+ $config->chatty = false;
+ $config->getHTMLDefinition();
+ $config->getHTMLDefinition(true);
+ }
+
+ function test_getHTMLDefinition_inconsistentOptimizedError() {
+ $this->expectError("Useless DefinitionID declaration");
+ $this->expectException(new HTMLPurifier_Exception("Inconsistent use of optimized and unoptimized raw definition retrievals"));
+ $config = HTMLPurifier_Config::create(array('HTML.DefinitionID' => 'HTMLPurifier_ConfigTest->test_getHTMLDefinition_inconsistentOptimizedError'));
+ $config->chatty = false;
+ $config->getHTMLDefinition(true, false);
+ $config->getHTMLDefinition(true, true);
+ }
+
+ function test_getHTMLDefinition_inconsistentOptimizedError2() {
+ $this->expectException(new HTMLPurifier_Exception("Inconsistent use of optimized and unoptimized raw definition retrievals"));
+ $config = HTMLPurifier_Config::create(array('HTML.DefinitionID' => 'HTMLPurifier_ConfigTest->test_getHTMLDefinition_inconsistentOptimizedError2'));
+ $config->chatty = false;
+ $config->getHTMLDefinition(true, true);
+ $config->getHTMLDefinition(true, false);
+ }
+
+ function test_getHTMLDefinition_rawError() {
+ $config = HTMLPurifier_Config::createDefault();
+ $this->expectException(new HTMLPurifier_Exception('Cannot retrieve raw version without specifying %HTML.DefinitionID'));
+ $def = $config->getHTMLDefinition(true, true);
+ }
+
+ function test_getCSSDefinition() {
+ $config = HTMLPurifier_Config::createDefault();
+ $def = $config->getCSSDefinition();
+ $this->assertIsA($def, 'HTMLPurifier_CSSDefinition');
+ }
+
+ function test_getDefinition() {
+ $this->schema->add('Cache.DefinitionImpl', null, 'string', true);
+ $config = new HTMLPurifier_Config($this->schema);
+ $this->expectException(new HTMLPurifier_Exception("Definition of Crust type not supported"));
+ $config->getDefinition('Crust');
+ }
+
+ function test_loadArray() {
+ // setup a few dummy namespaces/directives for our testing
+ $this->schema->add('Zoo.Aadvark', 0, 'int', false);
+ $this->schema->add('Zoo.Boar', 0, 'int', false);
+ $this->schema->add('Zoo.Camel', 0, 'int', false);
+ $this->schema->add('Zoo.Others', array(), 'list', false);
+
+ $config_manual = new HTMLPurifier_Config($this->schema);
+ $config_loadabbr = new HTMLPurifier_Config($this->schema);
+ $config_loadfull = new HTMLPurifier_Config($this->schema);
+
+ $config_manual->set('Zoo.Aadvark', 3);
+ $config_manual->set('Zoo.Boar', 5);
+ $config_manual->set('Zoo.Camel', 2000); // that's a lotta camels!
+ $config_manual->set('Zoo.Others', array('Peacock', 'Dodo')); // wtf!
+
+ // condensed form
+ $config_loadabbr->loadArray(array(
+ 'Zoo.Aadvark' => 3,
+ 'Zoo.Boar' => 5,
+ 'Zoo.Camel' => 2000,
+ 'Zoo.Others' => array('Peacock', 'Dodo')
+ ));
+
+ // fully expanded form
+ $config_loadfull->loadArray(array(
+ 'Zoo' => array(
+ 'Aadvark' => 3,
+ 'Boar' => 5,
+ 'Camel' => 2000,
+ 'Others' => array('Peacock', 'Dodo')
+ )
+ ));
+
+ $this->assertIdentical($config_manual, $config_loadabbr);
+ $this->assertIdentical($config_manual, $config_loadfull);
+
+ }
+
+ function test_create() {
+
+ $this->schema->add('Cake.Sprinkles', 666, 'int', false);
+ $this->schema->add('Cake.Flavor', 'vanilla', 'string', false);
+
+ $config = new HTMLPurifier_Config($this->schema);
+ $config->set('Cake.Sprinkles', 42);
+
+ // test flat pass-through
+ $created_config = HTMLPurifier_Config::create($config, $this->schema);
+ $this->assertIdentical($config, $created_config);
+
+ // test loadArray
+ $created_config = HTMLPurifier_Config::create(array('Cake.Sprinkles' => 42), $this->schema);
+ $this->assertIdentical($config, $created_config);
+
+ // test loadIni
+ $created_config = HTMLPurifier_Config::create(dirname(__FILE__) . '/ConfigTest-create.ini', $this->schema);
+ $this->assertIdentical($config, $created_config);
+
+ }
+
+ function test_finalize() {
+
+ // test finalization
+
+ $this->schema->add('Poem.Meter', 'iambic', 'string', false);
+
+ $config = new HTMLPurifier_Config($this->schema);
+ $config->autoFinalize = false;
+ $config->chatty = false;
+
+ $config->set('Poem.Meter', 'irregular');
+
+ $config->finalize();
+
+ $this->expectError('Cannot set directive after finalization');
+ $config->set('Poem.Meter', 'vedic');
+
+ $this->expectError('Cannot load directives after finalization');
+ $config->loadArray(array('Poem.Meter' => 'octosyllable'));
+
+ $this->expectError('Cannot load directives after finalization');
+ $config->loadIni(dirname(__FILE__) . '/ConfigTest-finalize.ini');
+
+ }
+
+ function test_loadArrayFromForm() {
+
+ $this->schema->add('Pancake.Mix', 'buttermilk', 'string', false);
+ $this->schema->add('Pancake.Served', true, 'bool', false);
+ $this->schema->add('Toppings.Syrup', true, 'bool', false);
+ $this->schema->add('Toppings.Flavor', 'maple', 'string', false);
+ $this->schema->add('Toppings.Strawberries', 3, 'int', false);
+ $this->schema->add('Toppings.Calories', 2000, 'int', true);
+ $this->schema->add('Toppings.DefinitionID', null, 'string', true);
+ $this->schema->add('Toppings.DefinitionRev', 1, 'int', false);
+ $this->schema->add('Toppings.Protected', 1, 'int', false);
+
+ $get = array(
+ 'breakfast' => array(
+ 'Pancake.Mix' => 'nasty',
+ 'Pancake.Served' => '0',
+ 'Toppings.Syrup' => '0',
+ 'Toppings.Flavor' => "juice",
+ 'Toppings.Strawberries' => '999',
+ 'Toppings.Calories' => '',
+ 'Null_Toppings.Calories' => '1',
+ 'Toppings.DefinitionID' => '<argh>',
+ 'Toppings.DefinitionRev' => '65',
+ 'Toppings.Protected' => '4',
+ )
+ );
+
+ $config_expect = HTMLPurifier_Config::create(array(
+ 'Pancake.Served' => false,
+ 'Toppings.Syrup' => false,
+ 'Toppings.Flavor' => "juice",
+ 'Toppings.Strawberries' => 999,
+ 'Toppings.Calories' => null
+ ), $this->schema);
+
+ $config_result = HTMLPurifier_Config::loadArrayFromForm(
+ $get, 'breakfast',
+ array('Pancake.Served', 'Toppings', '-Toppings.Protected'),
+ false, // mq fix
+ $this->schema
+ );
+
+ $this->assertEqual($config_expect, $config_result);
+
+ /*
+ MAGIC QUOTES NOT TESTED!!!
+
+ $get = array(
+ 'breakfast' => array(
+ 'Pancake.Mix' => 'n\\asty'
+ )
+ );
+ $config_expect = HTMLPurifier_Config::create(array(
+ 'Pancake.Mix' => 'n\\asty'
+ ));
+ $config_result = HTMLPurifier_Config::loadArrayFromForm($get, 'breakfast', true, false);
+ $this->assertEqual($config_expect, $config_result);
+ */
+ }
+
+ function test_getAllowedDirectivesForForm() {
+ $this->schema->add('Unused.Unused', 'Foobar', 'string', false);
+ $this->schema->add('Partial.Allowed', true, 'bool', false);
+ $this->schema->add('Partial.Unused', 'Foobar', 'string', false);
+ $this->schema->add('All.Allowed', true, 'bool', false);
+ $this->schema->add('All.Blacklisted', 'Foobar', 'string', false); // explicitly blacklisted
+ $this->schema->add('All.DefinitionID', 'Foobar', 'string', true); // auto-blacklisted
+ $this->schema->add('All.DefinitionRev', 2, 'int', false); // auto-blacklisted
+
+ $input = array('Partial.Allowed', 'All', '-All.Blacklisted');
+ $output = HTMLPurifier_Config::getAllowedDirectivesForForm($input, $this->schema);
+ $expect = array(
+ array('Partial', 'Allowed'),
+ array('All', 'Allowed')
+ );
+
+ $this->assertEqual($output, $expect);
+
+ }
+
+ function testDeprecatedAPI() {
+ $this->schema->add('Foo.Bar', 2, 'int', false);
+ $config = new HTMLPurifier_Config($this->schema);
+ $config->chatty = false;
+ $this->expectError('Using deprecated API: use $config->set(\'Foo.Bar\', ...) instead');
+ $config->set('Foo', 'Bar', 4);
+ $this->expectError('Using deprecated API: use $config->get(\'Foo.Bar\') instead');
+ $this->assertIdentical($config->get('Foo', 'Bar'), 4);
+ }
+
+ function testInherit() {
+ $this->schema->add('Phantom.Masked', 25, 'int', false);
+ $this->schema->add('Phantom.Unmasked', 89, 'int', false);
+ $this->schema->add('Phantom.Latemasked', 11, 'int', false);
+ $config = new HTMLPurifier_Config($this->schema);
+ $config->set('Phantom.Masked', 800);
+ $subconfig = HTMLPurifier_Config::inherit($config);
+ $config->set('Phantom.Latemasked', 100, 'int', false);
+ $this->assertIdentical($subconfig->get('Phantom.Masked'), 800);
+ $this->assertIdentical($subconfig->get('Phantom.Unmasked'), 89);
+ $this->assertIdentical($subconfig->get('Phantom.Latemasked'), 100);
+ }
+
+ function testSerialize() {
+ $config = HTMLPurifier_Config::createDefault();
+ $config->set('HTML.Allowed', 'a');
+ $config2 = unserialize($config->serialize());
+ $this->assertIdentical($config->get('HTML.Allowed'), $config2->get('HTML.Allowed'));
+ }
+
+ function testDefinitionCachingNothing() {
+ list($mock, $config) = $this->setupCacheMock('HTML');
+ // should not touch the cache
+ $mock->expectNever('get');
+ $mock->expectNever('add');
+ $mock->expectNever('set');
+ $config->getDefinition('HTML', true);
+ $config->getDefinition('HTML', true);
+ $config->getDefinition('HTML');
+ $this->teardownCacheMock();
+ }
+
+ function testDefinitionCachingOptimized() {
+ list($mock, $config) = $this->setupCacheMock('HTML');
+ $mock->expectNever('set');
+ $config->set('HTML.DefinitionID', 'HTMLPurifier_ConfigTest->testDefinitionCachingOptimized');
+ $mock->expectOnce('get');
+ $mock->setReturnValue('get', null);
+ $this->assertTrue($config->maybeGetRawHTMLDefinition());
+ $this->assertTrue($config->maybeGetRawHTMLDefinition());
+ $mock->expectOnce('add');
+ $config->getDefinition('HTML');
+ $this->teardownCacheMock();
+ }
+
+ function testDefinitionCachingOptimizedHit() {
+ $fake_config = HTMLPurifier_Config::createDefault();
+ $fake_def = $fake_config->getHTMLDefinition();
+ list($mock, $config) = $this->setupCacheMock('HTML');
+ // should never frob cache
+ $mock->expectNever('add');
+ $mock->expectNever('set');
+ $config->set('HTML.DefinitionID', 'HTMLPurifier_ConfigTest->testDefinitionCachingOptimizedHit');
+ $mock->expectOnce('get');
+ $mock->setReturnValue('get', $fake_def);
+ $this->assertNull($config->maybeGetRawHTMLDefinition());
+ $config->getDefinition('HTML');
+ $config->getDefinition('HTML');
+ $this->teardownCacheMock();
+ }
+
+ protected function setupCacheMock($type) {
+ // inject our definition cache mock globally (borrowed from
+ // DefinitionFactoryTest)
+ generate_mock_once("HTMLPurifier_DefinitionCacheFactory");
+ $factory = new HTMLPurifier_DefinitionCacheFactoryMock();
+ $this->oldFactory = HTMLPurifier_DefinitionCacheFactory::instance();
+ HTMLPurifier_DefinitionCacheFactory::instance($factory);
+ generate_mock_once("HTMLPurifier_DefinitionCache");
+ $mock = new HTMLPurifier_DefinitionCacheMock();
+ $config = HTMLPurifier_Config::createDefault();
+ $factory->setReturnValue('create', $mock, array($type, $config));
+ return array($mock, $config);
+ }
+ protected function teardownCacheMock() {
+ HTMLPurifier_DefinitionCacheFactory::instance($this->oldFactory);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ContextTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ContextTest.php
new file mode 100644
index 000000000..c5cef1651
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ContextTest.php
@@ -0,0 +1,84 @@
+<?php
+
+// mocks
+class HTMLPurifier_ContextTest extends HTMLPurifier_Harness
+{
+
+ protected $context;
+
+ public function setUp() {
+ $this->context = new HTMLPurifier_Context();
+ }
+
+ function testStandardUsage() {
+
+ generate_mock_once('HTMLPurifier_IDAccumulator');
+
+ $this->assertFalse($this->context->exists('IDAccumulator'));
+
+ $accumulator = new HTMLPurifier_IDAccumulatorMock();
+ $this->context->register('IDAccumulator', $accumulator);
+ $this->assertTrue($this->context->exists('IDAccumulator'));
+
+ $accumulator_2 =& $this->context->get('IDAccumulator');
+ $this->assertReference($accumulator, $accumulator_2);
+
+ $this->context->destroy('IDAccumulator');
+ $this->assertFalse($this->context->exists('IDAccumulator'));
+
+ $this->expectError('Attempted to retrieve non-existent variable IDAccumulator');
+ $accumulator_3 =& $this->context->get('IDAccumulator');
+ $this->assertNull($accumulator_3);
+
+ $this->expectError('Attempted to destroy non-existent variable IDAccumulator');
+ $this->context->destroy('IDAccumulator');
+
+ }
+
+ function testReRegister() {
+
+ $var = true;
+ $this->context->register('OnceOnly', $var);
+
+ $this->expectError('Name OnceOnly produces collision, cannot re-register');
+ $this->context->register('OnceOnly', $var);
+
+ // destroy it, now registration is okay
+ $this->context->destroy('OnceOnly');
+ $this->context->register('OnceOnly', $var);
+
+ }
+
+ function test_loadArray() {
+
+ // references can be *really* wonky!
+
+ $context_manual = new HTMLPurifier_Context();
+ $context_load = new HTMLPurifier_Context();
+
+ $var1 = 1;
+ $var2 = 2;
+
+ $context_manual->register('var1', $var1);
+ $context_manual->register('var2', $var2);
+
+ // you MUST set up the references when constructing the array,
+ // otherwise the registered version will be a copy
+ $array = array(
+ 'var1' => &$var1,
+ 'var2' => &$var2
+ );
+
+ $context_load->loadArray($array);
+ $this->assertIdentical($context_manual, $context_load);
+
+ $var1 = 10;
+ $var2 = 20;
+
+ $this->assertIdentical($context_manual, $context_load);
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/Decorator/CleanupTest.php b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/Decorator/CleanupTest.php
new file mode 100644
index 000000000..afcb0e39f
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/Decorator/CleanupTest.php
@@ -0,0 +1,56 @@
+<?php
+
+generate_mock_once('HTMLPurifier_DefinitionCache');
+
+class HTMLPurifier_DefinitionCache_Decorator_CleanupTest extends HTMLPurifier_DefinitionCache_DecoratorHarness
+{
+
+ function setup() {
+ $this->cache = new HTMLPurifier_DefinitionCache_Decorator_Cleanup();
+ parent::setup();
+ }
+
+ function setupMockForSuccess($op) {
+ $this->mock->expectOnce($op, array($this->def, $this->config));
+ $this->mock->setReturnValue($op, true, array($this->def, $this->config));
+ $this->mock->expectNever('cleanup');
+ }
+
+ function setupMockForFailure($op) {
+ $this->mock->expectOnce($op, array($this->def, $this->config));
+ $this->mock->setReturnValue($op, false, array($this->def, $this->config));
+ $this->mock->expectOnce('cleanup', array($this->config));
+ }
+
+ function test_get() {
+ $this->mock->expectOnce('get', array($this->config));
+ $this->mock->setReturnValue('get', true, array($this->config));
+ $this->mock->expectNever('cleanup');
+ $this->assertEqual($this->cache->get($this->config), $this->def);
+ }
+
+ function test_get_failure() {
+ $this->mock->expectOnce('get', array($this->config));
+ $this->mock->setReturnValue('get', false, array($this->config));
+ $this->mock->expectOnce('cleanup', array($this->config));
+ $this->assertEqual($this->cache->get($this->config), false);
+ }
+
+ function test_set() {
+ $this->setupMockForSuccess('set');
+ $this->assertEqual($this->cache->set($this->def, $this->config), true);
+ }
+
+ function test_replace() {
+ $this->setupMockForSuccess('replace');
+ $this->assertEqual($this->cache->replace($this->def, $this->config), true);
+ }
+
+ function test_add() {
+ $this->setupMockForSuccess('add');
+ $this->assertEqual($this->cache->add($this->def, $this->config), true);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/Decorator/MemoryTest.php b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/Decorator/MemoryTest.php
new file mode 100644
index 000000000..55f2f249c
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/Decorator/MemoryTest.php
@@ -0,0 +1,70 @@
+<?php
+
+generate_mock_once('HTMLPurifier_DefinitionCache');
+
+class HTMLPurifier_DefinitionCache_Decorator_MemoryTest extends HTMLPurifier_DefinitionCache_DecoratorHarness
+{
+
+ function setup() {
+ $this->cache = new HTMLPurifier_DefinitionCache_Decorator_Memory();
+ parent::setup();
+ }
+
+ function setupMockForSuccess($op) {
+ $this->mock->expectOnce($op, array($this->def, $this->config));
+ $this->mock->setReturnValue($op, true, array($this->def, $this->config));
+ $this->mock->expectNever('get');
+ }
+
+ function setupMockForFailure($op) {
+ $this->mock->expectOnce($op, array($this->def, $this->config));
+ $this->mock->setReturnValue($op, false, array($this->def, $this->config));
+ $this->mock->expectOnce('get', array($this->config));
+ }
+
+ function test_get() {
+ $this->mock->expectOnce('get', array($this->config)); // only ONE call!
+ $this->mock->setReturnValue('get', $this->def, array($this->config));
+ $this->assertEqual($this->cache->get($this->config), $this->def);
+ $this->assertEqual($this->cache->get($this->config), $this->def);
+ }
+
+ function test_set() {
+ $this->setupMockForSuccess('set', 'get');
+ $this->assertEqual($this->cache->set($this->def, $this->config), true);
+ $this->assertEqual($this->cache->get($this->config), $this->def);
+ }
+
+ function test_set_failure() {
+ $this->setupMockForFailure('set', 'get');
+ $this->assertEqual($this->cache->set($this->def, $this->config), false);
+ $this->cache->get($this->config);
+ }
+
+ function test_replace() {
+ $this->setupMockForSuccess('replace', 'get');
+ $this->assertEqual($this->cache->replace($this->def, $this->config), true);
+ $this->assertEqual($this->cache->get($this->config), $this->def);
+ }
+
+ function test_replace_failure() {
+ $this->setupMockForFailure('replace', 'get');
+ $this->assertEqual($this->cache->replace($this->def, $this->config), false);
+ $this->cache->get($this->config);
+ }
+
+ function test_add() {
+ $this->setupMockForSuccess('add', 'get');
+ $this->assertEqual($this->cache->add($this->def, $this->config), true);
+ $this->assertEqual($this->cache->get($this->config), $this->def);
+ }
+
+ function test_add_failure() {
+ $this->setupMockForFailure('add', 'get');
+ $this->assertEqual($this->cache->add($this->def, $this->config), false);
+ $this->cache->get($this->config);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/DecoratorHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/DecoratorHarness.php
new file mode 100644
index 000000000..64951a3ed
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/DecoratorHarness.php
@@ -0,0 +1,23 @@
+<?php
+
+generate_mock_once('HTMLPurifier_DefinitionCache');
+
+class HTMLPurifier_DefinitionCache_DecoratorHarness extends HTMLPurifier_DefinitionCacheHarness
+{
+
+ function setup() {
+ $this->mock = new HTMLPurifier_DefinitionCacheMock();
+ $this->mock->type = 'Test';
+ $this->cache = $this->cache->decorate($this->mock);
+ $this->def = $this->generateDefinition();
+ $this->config = $this->generateConfigMock();
+ }
+
+ function teardown() {
+ unset($this->mock);
+ unset($this->cache);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/DecoratorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/DecoratorTest.php
new file mode 100644
index 000000000..f64967970
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/DecoratorTest.php
@@ -0,0 +1,42 @@
+<?php
+
+class HTMLPurifier_DefinitionCache_DecoratorTest extends HTMLPurifier_DefinitionCacheHarness
+{
+
+ function test() {
+
+ generate_mock_once('HTMLPurifier_DefinitionCache');
+ $mock = new HTMLPurifier_DefinitionCacheMock();
+ $mock->type = 'Test';
+
+ $cache = new HTMLPurifier_DefinitionCache_Decorator();
+ $cache = $cache->decorate($mock);
+
+ $this->assertIdentical($cache->type, $mock->type);
+
+ $def = $this->generateDefinition();
+ $config = $this->generateConfigMock();
+
+ $mock->expectOnce('add', array($def, $config));
+ $cache->add($def, $config);
+
+ $mock->expectOnce('set', array($def, $config));
+ $cache->set($def, $config);
+
+ $mock->expectOnce('replace', array($def, $config));
+ $cache->replace($def, $config);
+
+ $mock->expectOnce('get', array($config));
+ $cache->get($config);
+
+ $mock->expectOnce('flush', array($config));
+ $cache->flush($config);
+
+ $mock->expectOnce('cleanup', array($config));
+ $cache->cleanup($config);
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/SerializerTest.php b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/SerializerTest.php
new file mode 100644
index 000000000..f6fec51ef
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/SerializerTest.php
@@ -0,0 +1,225 @@
+<?php
+
+class HTMLPurifier_DefinitionCache_SerializerTest extends HTMLPurifier_DefinitionCacheHarness
+{
+
+ function test() {
+ // XXX SimpleTest does some really crazy stuff in the background
+ // to do equality checks. Unfortunately, this makes some
+ // versions of PHP segfault. So we need to define a better,
+ // homebrew notion of equality and use that instead. For now,
+ // the identical asserts are commented out.
+
+ $cache = new HTMLPurifier_DefinitionCache_Serializer('Test');
+
+ $config = $this->generateConfigMock('serial');
+ $config->setReturnValue('get', 2, array('Test.DefinitionRev'));
+ $config->version = '1.0.0';
+
+ $config_md5 = '1.0.0,serial,2';
+
+ $file = realpath(
+ $rel_file = HTMLPURIFIER_PREFIX . '/HTMLPurifier/DefinitionCache/Serializer/Test/' .
+ $config_md5 . '.ser'
+ );
+ if($file && file_exists($file)) unlink($file); // prevent previous failures from causing problems
+
+ $this->assertIdentical($config_md5, $cache->generateKey($config));
+
+ $def_original = $this->generateDefinition();
+
+ $cache->add($def_original, $config);
+ $this->assertFileExist($rel_file);
+
+ $file_generated = $cache->generateFilePath($config);
+ $this->assertIdentical(realpath($rel_file), realpath($file_generated));
+
+ $def_1 = $cache->get($config);
+ // $this->assertIdentical($def_original, $def_1);
+
+ $def_original->info_random = 'changed';
+
+ $cache->set($def_original, $config);
+ $def_2 = $cache->get($config);
+
+ // $this->assertIdentical($def_original, $def_2);
+ // $this->assertNotEqual ($def_original, $def_1);
+
+ $def_original->info_random = 'did it change?';
+
+ $this->assertFalse($cache->add($def_original, $config));
+ $def_3 = $cache->get($config);
+
+ // $this->assertNotEqual ($def_original, $def_3); // did not change!
+ // $this->assertIdentical($def_3, $def_2);
+
+ $cache->replace($def_original, $config);
+ $def_4 = $cache->get($config);
+ // $this->assertIdentical($def_original, $def_4);
+
+ $cache->remove($config);
+ $this->assertFileNotExist($file);
+
+ $this->assertFalse($cache->replace($def_original, $config));
+ $def_5 = $cache->get($config);
+ $this->assertFalse($def_5);
+
+ }
+
+ function test_errors() {
+ $cache = new HTMLPurifier_DefinitionCache_Serializer('Test');
+ $def = $this->generateDefinition();
+ $def->setup = true;
+ $def->type = 'NotTest';
+ $config = $this->generateConfigMock('testfoo');
+
+ $this->expectError('Cannot use definition of type NotTest in cache for Test');
+ $cache->add($def, $config);
+
+ $this->expectError('Cannot use definition of type NotTest in cache for Test');
+ $cache->set($def, $config);
+
+ $this->expectError('Cannot use definition of type NotTest in cache for Test');
+ $cache->replace($def, $config);
+ }
+
+ function test_flush() {
+
+ $cache = new HTMLPurifier_DefinitionCache_Serializer('Test');
+
+ $config1 = $this->generateConfigMock('test1');
+ $config2 = $this->generateConfigMock('test2');
+ $config3 = $this->generateConfigMock('test3');
+
+ $def1 = $this->generateDefinition(array('info_candles' => 1));
+ $def2 = $this->generateDefinition(array('info_candles' => 2));
+ $def3 = $this->generateDefinition(array('info_candles' => 3));
+
+ $cache->add($def1, $config1);
+ $cache->add($def2, $config2);
+ $cache->add($def3, $config3);
+
+ $this->assertEqual($def1, $cache->get($config1));
+ $this->assertEqual($def2, $cache->get($config2));
+ $this->assertEqual($def3, $cache->get($config3));
+
+ $cache->flush($config1); // only essential directive is %Cache.SerializerPath
+
+ $this->assertFalse($cache->get($config1));
+ $this->assertFalse($cache->get($config2));
+ $this->assertFalse($cache->get($config3));
+
+ }
+
+ function testCleanup() {
+
+ $cache = new HTMLPurifier_DefinitionCache_Serializer('Test');
+
+ // in order of age, oldest first
+ // note that configurations are all identical, but version/revision
+ // are different
+
+ $config1 = $this->generateConfigMock();
+ $config1->version = '0.9.0';
+ $config1->setReturnValue('get', 574, array('Test.DefinitionRev'));
+ $def1 = $this->generateDefinition(array('info' => 1));
+
+ $config2 = $this->generateConfigMock();
+ $config2->version = '1.0.0beta';
+ $config2->setReturnValue('get', 1, array('Test.DefinitionRev'));
+ $def2 = $this->generateDefinition(array('info' => 3));
+
+ $cache->set($def1, $config1);
+ $cache->cleanup($config1);
+ $this->assertEqual($def1, $cache->get($config1)); // no change
+
+ $cache->cleanup($config2);
+ $this->assertFalse($cache->get($config1));
+ $this->assertFalse($cache->get($config2));
+
+ }
+
+ function testCleanupOnlySameID() {
+
+ $cache = new HTMLPurifier_DefinitionCache_Serializer('Test');
+
+ $config1 = $this->generateConfigMock('serial1');
+ $config1->version = '1.0.0';
+ $config1->setReturnValue('get', 1, array('Test.DefinitionRev'));
+ $def1 = $this->generateDefinition(array('info' => 1));
+
+ $config2 = $this->generateConfigMock('serial2');
+ $config2->version = '1.0.0';
+ $config2->setReturnValue('get', 34, array('Test.DefinitionRev'));
+ $def2 = $this->generateDefinition(array('info' => 3));
+
+ $cache->set($def1, $config1);
+ $cache->cleanup($config1);
+ $this->assertEqual($def1, $cache->get($config1)); // no change
+
+ $cache->set($def2, $config2);
+ $cache->cleanup($config2);
+ $this->assertEqual($def1, $cache->get($config1));
+ $this->assertEqual($def2, $cache->get($config2));
+
+ $cache->flush($config1);
+ }
+
+ /**
+ * Asserts that a file exists, ignoring the stat cache
+ */
+ function assertFileExist($file) {
+ clearstatcache();
+ $this->assertTrue(file_exists($file), 'Expected ' . $file . ' exists');
+ }
+
+ /**
+ * Asserts that a file does not exist, ignoring the stat cache
+ */
+ function assertFileNotExist($file) {
+ clearstatcache();
+ $this->assertFalse(file_exists($file), 'Expected ' . $file . ' does not exist');
+ }
+
+ function testAlternatePath() {
+
+ $cache = new HTMLPurifier_DefinitionCache_Serializer('Test');
+ $config = $this->generateConfigMock('serial');
+ $config->version = '1.0.0';
+ $config->setReturnValue('get', 1, array('Test.DefinitionRev'));
+ $dir = dirname(__FILE__) . '/SerializerTest';
+ $config->setReturnValue('get', $dir, array('Cache.SerializerPath'));
+
+ $def_original = $this->generateDefinition();
+ $cache->add($def_original, $config);
+ $this->assertFileExist($dir . '/Test/1.0.0,serial,1.ser');
+
+ unlink($dir . '/Test/1.0.0,serial,1.ser');
+ rmdir( $dir . '/Test');
+
+ }
+
+ function testAlternatePermissions() {
+
+ $cache = new HTMLPurifier_DefinitionCache_Serializer('Test');
+ $config = $this->generateConfigMock('serial');
+ $config->version = '1.0.0';
+ $config->setReturnValue('get', 1, array('Test.DefinitionRev'));
+ $dir = dirname(__FILE__) . '/SerializerTest';
+ $config->setReturnValue('get', $dir, array('Cache.SerializerPath'));
+ $config->setReturnValue('get', 0777, array('Cache.SerializerPermissions'));
+
+ $def_original = $this->generateDefinition();
+ $cache->add($def_original, $config);
+ $this->assertFileExist($dir . '/Test/1.0.0,serial,1.ser');
+
+ $this->assertEqual(0666, 0777 & fileperms($dir . '/Test/1.0.0,serial,1.ser'));
+ $this->assertEqual(0777, 0777 & fileperms($dir . '/Test'));
+
+ unlink($dir . '/Test/1.0.0,serial,1.ser');
+ rmdir( $dir . '/Test');
+
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/SerializerTest/README b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/SerializerTest/README
new file mode 100644
index 000000000..2e35c1c3d
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/SerializerTest/README
@@ -0,0 +1,3 @@
+This is a dummy file to prevent Git from ignoring this empty directory.
+
+ vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheFactoryTest.php b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheFactoryTest.php
new file mode 100644
index 000000000..d50ef8be1
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheFactoryTest.php
@@ -0,0 +1,70 @@
+<?php
+
+class HTMLPurifier_DefinitionCacheFactoryTest extends HTMLPurifier_Harness
+{
+
+ protected $factory;
+ protected $oldFactory;
+
+ public function setUp() {
+ parent::setup();
+ $this->factory = new HTMLPurifier_DefinitionCacheFactory();
+ $this->oldFactory = HTMLPurifier_DefinitionCacheFactory::instance();
+ HTMLPurifier_DefinitionCacheFactory::instance($this->factory);
+ }
+
+ public function tearDown() {
+ HTMLPurifier_DefinitionCacheFactory::instance($this->oldFactory);
+ }
+
+ function test_create() {
+ $cache = $this->factory->create('Test', $this->config);
+ $this->assertEqual($cache, new HTMLPurifier_DefinitionCache_Serializer('Test'));
+ }
+
+ function test_create_withDecorator() {
+ $this->factory->addDecorator('Memory');
+ $cache = $this->factory->create('Test', $this->config);
+ $cache_real = new HTMLPurifier_DefinitionCache_Decorator_Memory();
+ $cache_real = $cache_real->decorate(new HTMLPurifier_DefinitionCache_Serializer('Test'));
+ $this->assertEqual($cache, $cache_real);
+ }
+
+ function test_create_withDecoratorObject() {
+ $this->factory->addDecorator(new HTMLPurifier_DefinitionCache_Decorator_Memory());
+ $cache = $this->factory->create('Test', $this->config);
+ $cache_real = new HTMLPurifier_DefinitionCache_Decorator_Memory();
+ $cache_real = $cache_real->decorate(new HTMLPurifier_DefinitionCache_Serializer('Test'));
+ $this->assertEqual($cache, $cache_real);
+ }
+
+ function test_create_recycling() {
+ $cache = $this->factory->create('Test', $this->config);
+ $cache2 = $this->factory->create('Test', $this->config);
+ $this->assertReference($cache, $cache2);
+ }
+
+ function test_create_invalid() {
+ $this->config->set('Cache.DefinitionImpl', 'Invalid');
+ $this->expectError('Unrecognized DefinitionCache Invalid, using Serializer instead');
+ $cache = $this->factory->create('Test', $this->config);
+ $this->assertIsA($cache, 'HTMLPurifier_DefinitionCache_Serializer');
+ }
+
+ function test_null() {
+ $this->config->set('Cache.DefinitionImpl', null);
+ $cache = $this->factory->create('Test', $this->config);
+ $this->assertEqual($cache, new HTMLPurifier_DefinitionCache_Null('Test'));
+ }
+
+ function test_register() {
+ generate_mock_once('HTMLPurifier_DefinitionCache');
+ $this->config->set('Cache.DefinitionImpl', 'TestCache');
+ $this->factory->register('TestCache', $class = 'HTMLPurifier_DefinitionCacheMock');
+ $cache = $this->factory->create('Test', $this->config);
+ $this->assertIsA($cache, $class);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheHarness.php
new file mode 100644
index 000000000..ab3cef451
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheHarness.php
@@ -0,0 +1,34 @@
+<?php
+
+class HTMLPurifier_DefinitionCacheHarness extends HTMLPurifier_Harness
+{
+
+ /**
+ * Generate a configuration mock object that returns $values
+ * to a getBatch() call
+ * @param $values Values to return when getBatch is invoked
+ */
+ protected function generateConfigMock($serial = 'defaultserial') {
+ generate_mock_once('HTMLPurifier_Config');
+ $config = new HTMLPurifier_ConfigMock();
+ $config->setReturnValue('getBatchSerial', $serial, array('Test'));
+ $config->version = '1.0.0';
+ return $config;
+ }
+
+ /**
+ * Returns an anonymous def that has been setup and named Test
+ */
+ protected function generateDefinition($member_vars = array()) {
+ $def = new HTMLPurifier_DefinitionTestable();
+ $def->setup = true;
+ $def->type = 'Test';
+ foreach ($member_vars as $key => $val) {
+ $def->$key = $val;
+ }
+ return $def;
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheTest.php b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheTest.php
new file mode 100644
index 000000000..5ad2ff9cb
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheTest.php
@@ -0,0 +1,32 @@
+<?php
+
+class HTMLPurifier_DefinitionCacheTest extends HTMLPurifier_Harness
+{
+
+ function test_isOld() {
+ // using null subclass because parent is abstract
+ $cache = new HTMLPurifier_DefinitionCache_Null('Test');
+
+ generate_mock_once('HTMLPurifier_Config');
+ $config = new HTMLPurifier_ConfigMock();
+ $config->version = '1.0.0'; // hopefully no conflicts
+ $config->setReturnValue('get', 10, array('Test.DefinitionRev'));
+ $config->setReturnValue('getBatchSerial', 'hash', array('Test'));
+
+ $this->assertIdentical($cache->isOld('1.0.0,hash,10', $config), false);
+ $this->assertIdentical($cache->isOld('1.5.0,hash,1', $config), true);
+
+ $this->assertIdentical($cache->isOld('0.9.0,hash,1', $config), true);
+ $this->assertIdentical($cache->isOld('1.0.0,hash,1', $config), true);
+ $this->assertIdentical($cache->isOld('1.0.0beta,hash,11', $config), true);
+
+ $this->assertIdentical($cache->isOld('0.9.0,hash2,1', $config), true);
+ $this->assertIdentical($cache->isOld('1.0.0,hash2,1', $config), false); // if hash is different, don't touch!
+ $this->assertIdentical($cache->isOld('1.0.0beta,hash2,11', $config), true);
+ $this->assertIdentical($cache->isOld('1.0.0-dev,hash2,11', $config), true);
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionTest.php b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionTest.php
new file mode 100644
index 000000000..38676ad7a
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionTest.php
@@ -0,0 +1,20 @@
+<?php
+
+class HTMLPurifier_DefinitionTest extends HTMLPurifier_Harness
+{
+ function test_setup() {
+ $def = new HTMLPurifier_DefinitionTestable();
+ $config = HTMLPurifier_Config::createDefault();
+ $def->expectOnce('doSetup', array($config));
+ $def->setup($config);
+ }
+ function test_setup_redundant() {
+ $def = new HTMLPurifier_DefinitionTestable();
+ $config = HTMLPurifier_Config::createDefault();
+ $def->expectNever('doSetup');
+ $def->setup = true;
+ $def->setup($config);
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionTestable.php b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionTestable.php
new file mode 100644
index 000000000..312c57bb4
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionTestable.php
@@ -0,0 +1,8 @@
+<?php
+
+Mock::generatePartial(
+ 'HTMLPurifier_Definition',
+ 'HTMLPurifier_DefinitionTestable',
+ array('doSetup'));
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DoctypeRegistryTest.php b/lib/htmlpurifier/tests/HTMLPurifier/DoctypeRegistryTest.php
new file mode 100644
index 000000000..189003ace
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/DoctypeRegistryTest.php
@@ -0,0 +1,77 @@
+<?php
+
+class HTMLPurifier_DoctypeRegistryTest extends HTMLPurifier_Harness
+{
+
+ function test_register() {
+
+ $registry = new HTMLPurifier_DoctypeRegistry();
+
+ $d = $registry->register(
+ $name = 'XHTML 1.0 Transitional',
+ $xml = true,
+ $modules = array('module-one', 'module-two'),
+ $tidyModules = array('lenient-module'),
+ $aliases = array('X10T')
+ );
+
+ $d2 = new HTMLPurifier_Doctype($name, $xml, $modules, $tidyModules, $aliases);
+
+ $this->assertIdentical($d, $d2);
+ $this->assertSame($d, $registry->get('XHTML 1.0 Transitional'));
+
+ // test shorthand
+ $d = $registry->register(
+ $name = 'XHTML 1.0 Strict', true, 'module', 'Tidy', 'X10S'
+ );
+ $d2 = new HTMLPurifier_Doctype($name, true, array('module'), array('Tidy'), array('X10S'));
+
+ $this->assertIdentical($d, $d2);
+
+ }
+
+ function test_get() {
+
+ // see also alias and register tests
+
+ $registry = new HTMLPurifier_DoctypeRegistry();
+
+ $this->expectError('Doctype XHTML 2.0 does not exist');
+ $registry->get('XHTML 2.0');
+
+ // prevent XSS
+ $this->expectError('Doctype &lt;foo&gt; does not exist');
+ $registry->get('<foo>');
+
+ }
+
+ function testAliases() {
+
+ $registry = new HTMLPurifier_DoctypeRegistry();
+
+ $d1 = $registry->register('Doc1', true, array(), array(), array('1'));
+
+ $this->assertSame($d1, $registry->get('Doc1'));
+ $this->assertSame($d1, $registry->get('1'));
+
+ $d2 = $registry->register('Doc2', true, array(), array(), array('2'));
+
+ $this->assertSame($d2, $registry->get('Doc2'));
+ $this->assertSame($d2, $registry->get('2'));
+
+ $d3 = $registry->register('1', true, array(), array(), array());
+
+ // literal name overrides alias
+ $this->assertSame($d3, $registry->get('1'));
+
+ $d4 = $registry->register('One', true, array(), array(), array('1'));
+
+ $this->assertSame($d4, $registry->get('One'));
+ // still it overrides
+ $this->assertSame($d3, $registry->get('1'));
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ElementDefTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ElementDefTest.php
new file mode 100644
index 000000000..500312b3c
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ElementDefTest.php
@@ -0,0 +1,91 @@
+<?php
+
+class HTMLPurifier_ElementDefTest extends HTMLPurifier_Harness
+{
+
+ function test_mergeIn() {
+
+ $def1 = new HTMLPurifier_ElementDef();
+ $def2 = new HTMLPurifier_ElementDef();
+ $def3 = new HTMLPurifier_ElementDef();
+
+ $old = 1;
+ $new = 2;
+ $overloaded_old = 3;
+ $overloaded_new = 4;
+ $removed = 5;
+
+ $def1->standalone = true;
+ $def1->attr = array(
+ 0 => array('old-include'),
+ 'old-attr' => $old,
+ 'overloaded-attr' => $overloaded_old,
+ 'removed-attr' => $removed,
+ );
+ $def1->attr_transform_pre =
+ $def1->attr_transform_post = array(
+ 'old-transform' => $old,
+ 'overloaded-transform' => $overloaded_old,
+ 'removed-transform' => $removed,
+ );
+ $def1->child = $overloaded_old;
+ $def1->content_model = 'old';
+ $def1->content_model_type = $overloaded_old;
+ $def1->descendants_are_inline = false;
+ $def1->excludes = array(
+ 'old' => true,
+ 'removed-old' => true
+ );
+
+ $def2->standalone = false;
+ $def2->attr = array(
+ 0 => array('new-include'),
+ 'new-attr' => $new,
+ 'overloaded-attr' => $overloaded_new,
+ 'removed-attr' => false,
+ );
+ $def2->attr_transform_pre =
+ $def2->attr_transform_post = array(
+ 'new-transform' => $new,
+ 'overloaded-transform' => $overloaded_new,
+ 'removed-transform' => false,
+ );
+ $def2->child = $new;
+ $def2->content_model = '#SUPER | new';
+ $def2->content_model_type = $overloaded_new;
+ $def2->descendants_are_inline = true;
+ $def2->excludes = array(
+ 'new' => true,
+ 'removed-old' => false
+ );
+
+ $def1->mergeIn($def2);
+ $def1->mergeIn($def3); // empty, has no effect
+
+ $this->assertIdentical($def1->standalone, true);
+ $this->assertIdentical($def1->attr, array(
+ 0 => array('old-include', 'new-include'),
+ 'old-attr' => $old,
+ 'overloaded-attr' => $overloaded_new,
+ 'new-attr' => $new,
+ ));
+ $this->assertIdentical($def1->attr_transform_pre, $def1->attr_transform_post);
+ $this->assertIdentical($def1->attr_transform_pre, array(
+ 'old-transform' => $old,
+ 'overloaded-transform' => $overloaded_new,
+ 'new-transform' => $new,
+ ));
+ $this->assertIdentical($def1->child, $new);
+ $this->assertIdentical($def1->content_model, 'old | new');
+ $this->assertIdentical($def1->content_model_type, $overloaded_new);
+ $this->assertIdentical($def1->descendants_are_inline, true);
+ $this->assertIdentical($def1->excludes, array(
+ 'old' => true,
+ 'new' => true
+ ));
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/EncoderTest.php b/lib/htmlpurifier/tests/HTMLPurifier/EncoderTest.php
new file mode 100644
index 000000000..6084c39f3
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/EncoderTest.php
@@ -0,0 +1,215 @@
+<?php
+
+class HTMLPurifier_EncoderTest extends HTMLPurifier_Harness
+{
+
+ protected $_entity_lookup;
+
+ function setUp() {
+ $this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
+ parent::setUp();
+ }
+
+ function assertCleanUTF8($string, $expect = null) {
+ if ($expect === null) $expect = $string;
+ $this->assertIdentical(HTMLPurifier_Encoder::cleanUTF8($string), $expect, 'iconv: %s');
+ $this->assertIdentical(HTMLPurifier_Encoder::cleanUTF8($string, true), $expect, 'PHP: %s');
+ }
+
+ function test_cleanUTF8() {
+ $this->assertCleanUTF8('Normal string.');
+ $this->assertCleanUTF8("Test\tAllowed\nControl\rCharacters");
+ $this->assertCleanUTF8("null byte: \0", 'null byte: ');
+ $this->assertCleanUTF8("\1\2\3\4\5\6\7", '');
+ $this->assertCleanUTF8("\x7F", ''); // one byte invalid SGML char
+ $this->assertCleanUTF8("\xC2\x80", ''); // two byte invalid SGML
+ $this->assertCleanUTF8("\xF3\xBF\xBF\xBF"); // valid four byte
+ $this->assertCleanUTF8("\xDF\xFF", ''); // malformed UTF8
+ // invalid codepoints
+ $this->assertCleanUTF8("\xED\xB0\x80", '');
+ }
+
+ function test_convertToUTF8_noConvert() {
+ // UTF-8 means that we don't touch it
+ $this->assertIdentical(
+ HTMLPurifier_Encoder::convertToUTF8("\xF6", $this->config, $this->context),
+ "\xF6", // this is invalid
+ 'Expected identical [Binary: F6]'
+ );
+ }
+
+ function test_convertToUTF8_spuriousEncoding() {
+ $this->config->set('Core.Encoding', 'utf99');
+ $this->expectError('Invalid encoding utf99');
+ $this->assertIdentical(
+ HTMLPurifier_Encoder::convertToUTF8("\xF6", $this->config, $this->context),
+ ''
+ );
+ }
+
+ function test_convertToUTF8_iso8859_1() {
+ $this->config->set('Core.Encoding', 'ISO-8859-1');
+ $this->assertIdentical(
+ HTMLPurifier_Encoder::convertToUTF8("\xF6", $this->config, $this->context),
+ "\xC3\xB6"
+ );
+ }
+
+ function test_convertToUTF8_withoutIconv() {
+ $this->config->set('Core.Encoding', 'ISO-8859-1');
+ $this->config->set('Test.ForceNoIconv', true);
+ $this->assertIdentical(
+ HTMLPurifier_Encoder::convertToUTF8("\xF6", $this->config, $this->context),
+ "\xC3\xB6"
+ );
+
+ }
+
+ function getZhongWen() {
+ return "\xE4\xB8\xAD\xE6\x96\x87 (Chinese)";
+ }
+
+ function test_convertFromUTF8_utf8() {
+ // UTF-8 means that we don't touch it
+ $this->assertIdentical(
+ HTMLPurifier_Encoder::convertFromUTF8("\xC3\xB6", $this->config, $this->context),
+ "\xC3\xB6"
+ );
+ }
+
+ function test_convertFromUTF8_iso8859_1() {
+ $this->config->set('Core.Encoding', 'ISO-8859-1');
+ $this->assertIdentical(
+ HTMLPurifier_Encoder::convertFromUTF8("\xC3\xB6", $this->config, $this->context),
+ "\xF6",
+ 'Expected identical [Binary: F6]'
+ );
+ }
+
+ function test_convertFromUTF8_iconvNoChars() {
+ if (!function_exists('iconv')) return;
+ $this->config->set('Core.Encoding', 'ISO-8859-1');
+ $this->assertIdentical(
+ HTMLPurifier_Encoder::convertFromUTF8($this->getZhongWen(), $this->config, $this->context),
+ " (Chinese)"
+ );
+ }
+
+ function test_convertFromUTF8_phpNormal() {
+ // Plain PHP implementation has slightly different behavior
+ $this->config->set('Core.Encoding', 'ISO-8859-1');
+ $this->config->set('Test.ForceNoIconv', true);
+ $this->assertIdentical(
+ HTMLPurifier_Encoder::convertFromUTF8("\xC3\xB6", $this->config, $this->context),
+ "\xF6",
+ 'Expected identical [Binary: F6]'
+ );
+ }
+
+ function test_convertFromUTF8_phpNoChars() {
+ $this->config->set('Core.Encoding', 'ISO-8859-1');
+ $this->config->set('Test.ForceNoIconv', true);
+ $this->assertIdentical(
+ HTMLPurifier_Encoder::convertFromUTF8($this->getZhongWen(), $this->config, $this->context),
+ "?? (Chinese)"
+ );
+ }
+
+ function test_convertFromUTF8_withProtection() {
+ // Preserve the characters!
+ $this->config->set('Core.Encoding', 'ISO-8859-1');
+ $this->config->set('Core.EscapeNonASCIICharacters', true);
+ $this->assertIdentical(
+ HTMLPurifier_Encoder::convertFromUTF8($this->getZhongWen(), $this->config, $this->context),
+ "&#20013;&#25991; (Chinese)"
+ );
+ }
+
+ function test_convertFromUTF8_withProtectionButUtf8() {
+ // Preserve the characters!
+ $this->config->set('Core.EscapeNonASCIICharacters', true);
+ $this->assertIdentical(
+ HTMLPurifier_Encoder::convertFromUTF8($this->getZhongWen(), $this->config, $this->context),
+ "&#20013;&#25991; (Chinese)"
+ );
+ }
+
+ function test_convertToASCIIDumbLossless() {
+
+ // Uppercase thorn letter
+ $this->assertIdentical(
+ HTMLPurifier_Encoder::convertToASCIIDumbLossless("\xC3\x9Eorn"),
+ "&#222;orn"
+ );
+
+ $this->assertIdentical(
+ HTMLPurifier_Encoder::convertToASCIIDumbLossless("an"),
+ "an"
+ );
+
+ // test up to four bytes
+ $this->assertIdentical(
+ HTMLPurifier_Encoder::convertToASCIIDumbLossless("\xF3\xA0\x80\xA0"),
+ "&#917536;"
+ );
+
+ }
+
+ function assertASCIISupportCheck($enc, $ret) {
+ $test = HTMLPurifier_Encoder::testEncodingSupportsASCII($enc, true);
+ if ($test === false) return;
+ $this->assertIdentical(
+ HTMLPurifier_Encoder::testEncodingSupportsASCII($enc),
+ $ret
+ );
+ $this->assertIdentical(
+ HTMLPurifier_Encoder::testEncodingSupportsASCII($enc, true),
+ $ret
+ );
+ }
+
+ function test_testEncodingSupportsASCII() {
+ $this->assertASCIISupportCheck('Shift_JIS', array("\xC2\xA5" => '\\', "\xE2\x80\xBE" => '~'));
+ $this->assertASCIISupportCheck('JOHAB', array("\xE2\x82\xA9" => '\\'));
+ $this->assertASCIISupportCheck('ISO-8859-1', array());
+ $this->assertASCIISupportCheck('dontexist', array()); // canary
+ }
+
+ function testShiftJIS() {
+ if (!function_exists('iconv')) return;
+ $this->config->set('Core.Encoding', 'Shift_JIS');
+ // This actually looks like a Yen, but we're going to treat it differently
+ $this->assertIdentical(
+ HTMLPurifier_Encoder::convertFromUTF8('\\~', $this->config, $this->context),
+ '\\~'
+ );
+ $this->assertIdentical(
+ HTMLPurifier_Encoder::convertToUTF8('\\~', $this->config, $this->context),
+ '\\~'
+ );
+ }
+
+ function testIconvTruncateBug() {
+ if (!function_exists('iconv')) return;
+ if (HTMLPurifier_Encoder::testIconvTruncateBug() !== HTMLPurifier_Encoder::ICONV_TRUNCATES) return;
+ $this->config->set('Core.Encoding', 'ISO-8859-1');
+ $this->assertIdentical(
+ HTMLPurifier_Encoder::convertFromUTF8("\xE4\xB8\xAD" . str_repeat('a', 10000), $this->config, $this->context),
+ str_repeat('a', 10000)
+ );
+ }
+
+ function testIconvChunking() {
+ if (!function_exists('iconv')) return;
+ if (HTMLPurifier_Encoder::testIconvTruncateBug() !== HTMLPurifier_Encoder::ICONV_TRUNCATES) return;
+ $this->assertIdentical(HTMLPurifier_Encoder::iconv('utf-8', 'iso-8859-1//IGNORE', "a\xF3\xA0\x80\xA0b", 4), 'ab');
+ $this->assertIdentical(HTMLPurifier_Encoder::iconv('utf-8', 'iso-8859-1//IGNORE', "aa\xE4\xB8\xADb", 4), 'aab');
+ $this->assertIdentical(HTMLPurifier_Encoder::iconv('utf-8', 'iso-8859-1//IGNORE', "aaa\xCE\xB1b", 4), 'aaab');
+ $this->assertIdentical(HTMLPurifier_Encoder::iconv('utf-8', 'iso-8859-1//IGNORE', "aaaa\xF3\xA0\x80\xA0b", 4), 'aaaab');
+ $this->assertIdentical(HTMLPurifier_Encoder::iconv('utf-8', 'iso-8859-1//IGNORE', "aaaa\xE4\xB8\xADb", 4), 'aaaab');
+ $this->assertIdentical(HTMLPurifier_Encoder::iconv('utf-8', 'iso-8859-1//IGNORE', "aaaa\xCE\xB1b", 4), 'aaaab');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/EntityLookupTest.php b/lib/htmlpurifier/tests/HTMLPurifier/EntityLookupTest.php
new file mode 100644
index 000000000..e715f64a2
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/EntityLookupTest.php
@@ -0,0 +1,27 @@
+<?php
+
+// this page is UTF-8 encoded!
+
+class HTMLPurifier_EntityLookupTest extends HTMLPurifier_Harness
+{
+
+ function test() {
+
+ $lookup = HTMLPurifier_EntityLookup::instance();
+
+ // latin char
+ $this->assertIdentical('â', $lookup->table['acirc']);
+
+ // special char
+ $this->assertIdentical('"', $lookup->table['quot']);
+ $this->assertIdentical('“', $lookup->table['ldquo']);
+ $this->assertIdentical('<', $lookup->table['lt']); // expressed strangely in source file
+
+ // symbol char
+ $this->assertIdentical('θ', $lookup->table['theta']);
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/EntityParserTest.php b/lib/htmlpurifier/tests/HTMLPurifier/EntityParserTest.php
new file mode 100644
index 000000000..17ef62a18
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/EntityParserTest.php
@@ -0,0 +1,85 @@
+<?php
+
+class HTMLPurifier_EntityParserTest extends HTMLPurifier_Harness
+{
+
+ protected $EntityParser;
+
+ public function setUp() {
+ $this->EntityParser = new HTMLPurifier_EntityParser();
+ $this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
+ }
+
+ function test_substituteNonSpecialEntities() {
+ $char_theta = $this->_entity_lookup->table['theta'];
+ $this->assertIdentical($char_theta,
+ $this->EntityParser->substituteNonSpecialEntities('&theta;') );
+ $this->assertIdentical('"',
+ $this->EntityParser->substituteNonSpecialEntities('"') );
+
+ // numeric tests, adapted from Feyd
+ $args = array();
+ $args[] = array(1114112,false );
+ $args[] = array(1114111,'F48FBFBF'); // 0x0010FFFF
+ $args[] = array(1048576,'F4808080'); // 0x00100000
+ $args[] = array(1048575,'F3BFBFBF'); // 0x000FFFFF
+ $args[] = array(262144, 'F1808080'); // 0x00040000
+ $args[] = array(262143, 'F0BFBFBF'); // 0x0003FFFF
+ $args[] = array(65536, 'F0908080'); // 0x00010000
+ $args[] = array(65535, 'EFBFBF' ); // 0x0000FFFF
+ $args[] = array(57344, 'EE8080' ); // 0x0000E000
+ $args[] = array(57343, false ); // 0x0000DFFF these are ill-formed
+ $args[] = array(56040, false ); // 0x0000DAE8 these are ill-formed
+ $args[] = array(55296, false ); // 0x0000D800 these are ill-formed
+ $args[] = array(55295, 'ED9FBF' ); // 0x0000D7FF
+ $args[] = array(53248, 'ED8080' ); // 0x0000D000
+ $args[] = array(53247, 'ECBFBF' ); // 0x0000CFFF
+ $args[] = array(4096, 'E18080' ); // 0x00001000
+ $args[] = array(4095, 'E0BFBF' ); // 0x00000FFF
+ $args[] = array(2048, 'E0A080' ); // 0x00000800
+ $args[] = array(2047, 'DFBF' ); // 0x000007FF
+ $args[] = array(128, 'C280' ); // 0x00000080 invalid SGML char
+ $args[] = array(127, '7F' ); // 0x0000007F invalid SGML char
+ $args[] = array(0, '00' ); // 0x00000000 invalid SGML char
+
+ $args[] = array(20108, 'E4BA8C' ); // 0x00004E8C
+ $args[] = array(77, '4D' ); // 0x0000004D
+ $args[] = array(66306, 'F0908C82'); // 0x00010302
+ $args[] = array(1072, 'D0B0' ); // 0x00000430
+
+ foreach ($args as $arg) {
+ $string = '&#' . $arg[0] . ';' . // decimal
+ '&#x' . dechex($arg[0]) . ';'; // hex
+ $expect = '';
+ if ($arg[1] !== false) {
+ // this is only for PHP 5, the below is PHP 5 and PHP 4
+ //$chars = str_split($arg[1], 2);
+ $chars = array();
+ // strlen must be called in loop because strings size changes
+ for ($i = 0; strlen($arg[1]) > $i; $i += 2) {
+ $chars[] = $arg[1][$i] . $arg[1][$i+1];
+ }
+ foreach ($chars as $char) {
+ $expect .= chr(hexdec($char));
+ }
+ $expect .= $expect; // double it
+ }
+ $this->assertIdentical(
+ $this->EntityParser->substituteNonSpecialEntities($string),
+ $expect,
+ 'Identical expectation [Hex: '. dechex($arg[0]) .']'
+ );
+ }
+
+ }
+
+ function test_substituteSpecialEntities() {
+ $this->assertIdentical(
+ "'",
+ $this->EntityParser->substituteSpecialEntities('&#39;')
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ErrorCollectorEMock.php b/lib/htmlpurifier/tests/HTMLPurifier/ErrorCollectorEMock.php
new file mode 100644
index 000000000..bbd096569
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ErrorCollectorEMock.php
@@ -0,0 +1,48 @@
+<?php
+
+generate_mock_once('HTMLPurifier_ErrorCollector');
+
+/**
+ * Extended error collector mock that has the ability to expect context
+ */
+class HTMLPurifier_ErrorCollectorEMock extends HTMLPurifier_ErrorCollectorMock
+{
+
+ private $_context;
+ private $_expected_context = array();
+ private $_expected_context_at = array();
+
+ public function prepare($context) {
+ $this->_context = $context;
+ }
+
+ public function expectContext($key, $value) {
+ $this->_expected_context[$key] = $value;
+ }
+ public function expectContextAt($step, $key, $value) {
+ $this->_expected_context_at[$step][$key] = $value;
+ }
+
+ public function send($v1, $v2) {
+ // test for context
+ $context = SimpleTest::getContext();
+ $test = $context->getTest();
+ $mock = $this->mock;
+
+ foreach ($this->_expected_context as $key => $value) {
+ $test->assertEqual($value, $this->_context->get($key));
+ }
+ $step = $mock->getCallCount('send');
+ if (isset($this->_expected_context_at[$step])) {
+ foreach ($this->_expected_context_at[$step] as $key => $value) {
+ $test->assertEqual($value, $this->_context->get($key));
+ }
+ }
+ // boilerplate mock code, does not have return value or references
+ $args = func_get_args();
+ $mock->invoke('send', $args);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ErrorCollectorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ErrorCollectorTest.php
new file mode 100644
index 000000000..09becba53
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ErrorCollectorTest.php
@@ -0,0 +1,157 @@
+<?php
+
+/**
+ * @warning HTML output is in flux, but eventually needs to be stabilized.
+ */
+class HTMLPurifier_ErrorCollectorTest extends HTMLPurifier_Harness
+{
+
+ protected $language, $generator, $line;
+ protected $collector;
+
+ public function setup() {
+ generate_mock_once('HTMLPurifier_Language');
+ generate_mock_once('HTMLPurifier_Generator');
+ parent::setup();
+ $this->language = new HTMLPurifier_LanguageMock();
+ $this->language->setReturnValue('getErrorName', 'Error', array(E_ERROR));
+ $this->language->setReturnValue('getErrorName', 'Warning', array(E_WARNING));
+ $this->language->setReturnValue('getErrorName', 'Notice', array(E_NOTICE));
+ // this might prove to be troublesome if we need to set config
+ $this->generator = new HTMLPurifier_Generator($this->config, $this->context);
+ $this->line = false;
+ $this->context->register('Locale', $this->language);
+ $this->context->register('CurrentLine', $this->line);
+ $this->context->register('Generator', $this->generator);
+ $this->collector = new HTMLPurifier_ErrorCollector($this->context);
+ }
+
+ function test() {
+
+ $language = $this->language;
+ $language->setReturnValue('getMessage', 'Message 1', array('message-1'));
+ $language->setReturnValue('formatMessage', 'Message 2', array('message-2', array(1 => 'param')));
+ $language->setReturnValue('formatMessage', ' at line 23', array('ErrorCollector: At line', array('line' => 23)));
+ $language->setReturnValue('formatMessage', ' at line 3', array('ErrorCollector: At line', array('line' => 3)));
+
+ $this->line = 23;
+ $this->collector->send(E_ERROR, 'message-1');
+
+ $this->line = 3;
+ $this->collector->send(E_WARNING, 'message-2', 'param');
+
+ $result = array(
+ 0 => array(23, E_ERROR, 'Message 1', array()),
+ 1 => array(3, E_WARNING, 'Message 2', array())
+ );
+
+ $this->assertIdentical($this->collector->getRaw(), $result);
+
+ /*
+ $formatted_result =
+ '<ul><li><strong>Warning</strong>: Message 2 at line 3</li>'.
+ '<li><strong>Error</strong>: Message 1 at line 23</li></ul>';
+
+ $this->assertIdentical($this->collector->getHTMLFormatted($this->config), $formatted_result);
+ */
+
+ }
+
+ function testNoErrors() {
+ $this->language->setReturnValue('getMessage', 'No errors', array('ErrorCollector: No errors'));
+
+ $formatted_result = '<p>No errors</p>';
+ $this->assertIdentical(
+ $this->collector->getHTMLFormatted($this->config),
+ $formatted_result
+ );
+ }
+
+ function testNoLineNumbers() {
+ $this->language->setReturnValue('getMessage', 'Message 1', array('message-1'));
+ $this->language->setReturnValue('getMessage', 'Message 2', array('message-2'));
+
+ $this->collector->send(E_ERROR, 'message-1');
+ $this->collector->send(E_ERROR, 'message-2');
+
+ $result = array(
+ 0 => array(false, E_ERROR, 'Message 1', array()),
+ 1 => array(false, E_ERROR, 'Message 2', array())
+ );
+ $this->assertIdentical($this->collector->getRaw(), $result);
+
+ /*
+ $formatted_result =
+ '<ul><li><strong>Error</strong>: Message 1</li>'.
+ '<li><strong>Error</strong>: Message 2</li></ul>';
+ $this->assertIdentical($this->collector->getHTMLFormatted($this->config), $formatted_result);
+ */
+ }
+
+ function testContextSubstitutions() {
+
+ $current_token = false;
+ $this->context->register('CurrentToken', $current_token);
+
+ // 0
+ $current_token = new HTMLPurifier_Token_Start('a', array('href' => 'http://example.com'), 32);
+ $this->language->setReturnValue('formatMessage', 'Token message',
+ array('message-data-token', array('CurrentToken' => $current_token)));
+ $this->collector->send(E_NOTICE, 'message-data-token');
+
+ $current_attr = 'href';
+ $this->language->setReturnValue('formatMessage', '$CurrentAttr.Name => $CurrentAttr.Value',
+ array('message-attr', array('CurrentToken' => $current_token)));
+
+ // 1
+ $this->collector->send(E_NOTICE, 'message-attr'); // test when context isn't available
+
+ // 2
+ $this->context->register('CurrentAttr', $current_attr);
+ $this->collector->send(E_NOTICE, 'message-attr');
+
+ $result = array(
+ 0 => array(32, E_NOTICE, 'Token message', array()),
+ 1 => array(32, E_NOTICE, '$CurrentAttr.Name => $CurrentAttr.Value', array()),
+ 2 => array(32, E_NOTICE, 'href => http://example.com', array())
+ );
+ $this->assertIdentical($this->collector->getRaw(), $result);
+
+ }
+
+ /*
+ function testNestedErrors() {
+ $this->language->setReturnValue('getMessage', 'Message 1', array('message-1'));
+ $this->language->setReturnValue('getMessage', 'Message 2', array('message-2'));
+ $this->language->setReturnValue('formatMessage', 'End Message', array('end-message', array(1 => 'param')));
+ $this->language->setReturnValue('formatMessage', ' at line 4', array('ErrorCollector: At line', array('line' => 4)));
+
+ $this->line = 4;
+ $this->collector->start();
+ $this->collector->send(E_WARNING, 'message-1');
+ $this->collector->send(E_NOTICE, 'message-2');
+ $this->collector->end(E_NOTICE, 'end-message', 'param');
+
+ $expect = array(
+ 0 => array(4, E_NOTICE, 'End Message', array(
+ 0 => array(4, E_WARNING, 'Message 1', array()),
+ 1 => array(4, E_NOTICE, 'Message 2', array()),
+ )),
+ );
+ $result = $this->collector->getRaw();
+ $this->assertIdentical($result, $expect);
+
+ $formatted_expect =
+ '<ul><li><strong>Notice</strong>: End Message at line 4<ul>'.
+ '<li><strong>Warning</strong>: Message 1 at line 4</li>'.
+ '<li><strong>Notice</strong>: Message 2 at line 4</li></ul>'.
+ '</li></ul>';
+ $formatted_result = $this->collector->getHTMLFormatted($this->config);
+ $this->assertIdentical($formatted_result, $formatted_expect);
+
+ }
+ */
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ErrorsHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/ErrorsHarness.php
new file mode 100644
index 000000000..f7c673ca5
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/ErrorsHarness.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @todo Make the callCount variable actually work, so we can precisely
+ * specify what errors we want: no more, no less
+ */
+class HTMLPurifier_ErrorsHarness extends HTMLPurifier_Harness
+{
+
+ protected $config, $context;
+ protected $collector, $generator, $callCount;
+
+ public function setup() {
+ $this->config = HTMLPurifier_Config::create(array('Core.CollectErrors' => true));
+ $this->context = new HTMLPurifier_Context();
+ generate_mock_once('HTMLPurifier_ErrorCollector');
+ $this->collector = new HTMLPurifier_ErrorCollectorEMock();
+ $this->collector->prepare($this->context);
+ $this->context->register('ErrorCollector', $this->collector);
+ $this->callCount = 0;
+ }
+
+ protected function expectNoErrorCollection() {
+ $this->collector->expectNever('send');
+ }
+
+ protected function expectErrorCollection() {
+ $args = func_get_args();
+ $this->collector->expectOnce('send', $args);
+ }
+
+ protected function expectContext($key, $value) {
+ $this->collector->expectContext($key, $value);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Filter/ExtractStyleBlocksTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Filter/ExtractStyleBlocksTest.php
new file mode 100644
index 000000000..3466d6aa2
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Filter/ExtractStyleBlocksTest.php
@@ -0,0 +1,231 @@
+<?php
+
+/**
+ * @todo Assimilate CSSTidy into our library
+ */
+class HTMLPurifier_Filter_ExtractStyleBlocksTest extends HTMLPurifier_Harness
+{
+
+ // usual use case:
+ function test_tokenizeHTML_extractStyleBlocks() {
+ $this->config->set('Filter.ExtractStyleBlocks', true);
+ $purifier = new HTMLPurifier($this->config);
+ $result = $purifier->purify('<style type="text/css">.foo {text-align:center;bogus:remove-me;} body.class[foo="attr"] {text-align:right;}</style>Test<style>* {font-size:12pt;}</style>');
+ $this->assertIdentical($result, 'Test');
+ $this->assertIdentical($purifier->context->get('StyleBlocks'),
+ array(
+ ".foo {\ntext-align:center;\n}",
+ "* {\nfont-size:12pt;\n}"
+ )
+ );
+ }
+
+ function assertExtractStyleBlocks($html, $expect = true, $styles = array()) {
+ $filter = new HTMLPurifier_Filter_ExtractStyleBlocks(); // disable cleaning
+ if ($expect === true) $expect = $html;
+ $this->config->set('Filter.ExtractStyleBlocks.TidyImpl', false);
+ $result = $filter->preFilter($html, $this->config, $this->context);
+ $this->assertIdentical($result, $expect);
+ $this->assertIdentical($this->context->get('StyleBlocks'), $styles);
+ }
+
+ function test_extractStyleBlocks_preserve() {
+ $this->assertExtractStyleBlocks('Foobar');
+ }
+
+ function test_extractStyleBlocks_allStyle() {
+ $this->assertExtractStyleBlocks('<style>foo</style>', '', array('foo'));
+ }
+
+ function test_extractStyleBlocks_multipleBlocks() {
+ $this->assertExtractStyleBlocks(
+ "<style>1</style><style>2</style>NOP<style>4</style>",
+ "NOP",
+ array('1', '2', '4')
+ );
+ }
+
+ function test_extractStyleBlocks_blockWithAttributes() {
+ $this->assertExtractStyleBlocks(
+ '<style type="text/css">css</style>',
+ '',
+ array('css')
+ );
+ }
+
+ function test_extractStyleBlocks_styleWithPadding() {
+ $this->assertExtractStyleBlocks(
+ "Alas<styled>Awesome</styled>\n<style>foo</style> Trendy!",
+ "Alas<styled>Awesome</styled>\n Trendy!",
+ array('foo')
+ );
+ }
+
+ function assertCleanCSS($input, $expect = true) {
+ $filter = new HTMLPurifier_Filter_ExtractStyleBlocks();
+ if ($expect === true) $expect = $input;
+ $this->normalize($input);
+ $this->normalize($expect);
+ $result = $filter->cleanCSS($input, $this->config, $this->context);
+ $this->assertIdentical($result, $expect);
+ }
+
+ function test_cleanCSS_malformed() {
+ $this->assertCleanCSS('</style>', '');
+ }
+
+ function test_cleanCSS_selector() {
+ $this->assertCleanCSS("a .foo #id div.cl#foo {\nfont-weight:700;\n}");
+ }
+
+ function test_cleanCSS_angledBrackets() {
+ // [Content] No longer can smuggle in angled brackets using
+ // font-family; when we add support for 'content', reinstate
+ // this test.
+ //$this->assertCleanCSS(
+ // ".class {\nfont-family:'</style>';\n}",
+ // ".class {\nfont-family:\"\\3C /style\\3E \";\n}"
+ //);
+ }
+
+ function test_cleanCSS_angledBrackets2() {
+ // CSSTidy's behavior in this case is wrong, and should be fixed
+ //$this->assertCleanCSS(
+ // "span[title=\"</style>\"] {\nfont-size:12pt;\n}",
+ // "span[title=\"\\3C /style\\3E \"] {\nfont-size:12pt;\n}"
+ //);
+ }
+
+ function test_cleanCSS_bogus() {
+ $this->assertCleanCSS("div {bogus:tree;}", "div {\n}");
+ }
+
+ /* [CONTENT]
+ function test_cleanCSS_escapeCodes() {
+ $this->assertCleanCSS(
+ ".class {\nfont-family:\"\\3C /style\\3E \";\n}"
+ );
+ }
+
+ function test_cleanCSS_noEscapeCodes() {
+ $this->config->set('Filter.ExtractStyleBlocks.Escaping', false);
+ $this->assertCleanCSS(
+ ".class {\nfont-family:\"</style>\";\n}"
+ );
+ }
+ */
+
+ function test_cleanCSS_scope() {
+ $this->config->set('Filter.ExtractStyleBlocks.Scope', '#foo');
+ $this->assertCleanCSS(
+ "p {\ntext-indent:1em;\n}",
+ "#foo p {\ntext-indent:1em;\n}"
+ );
+ }
+
+ function test_cleanCSS_scopeWithSelectorCommas() {
+ $this->config->set('Filter.ExtractStyleBlocks.Scope', '#foo');
+ $this->assertCleanCSS(
+ "b, i {\ntext-decoration:underline;\n}",
+ "#foo b, #foo i {\ntext-decoration:underline;\n}"
+ );
+ }
+
+ function test_cleanCSS_scopeWithNaughtySelector() {
+ $this->config->set('Filter.ExtractStyleBlocks.Scope', '#foo');
+ $this->assertCleanCSS(" + p {\ntext-indent:1em;\n}", '');
+ }
+
+ function test_cleanCSS_scopeWithMultipleNaughtySelectors() {
+ $this->config->set('Filter.ExtractStyleBlocks.Scope', '#foo');
+ $this->assertCleanCSS(" ++ ++ p {\ntext-indent:1em;\n}", '');
+ }
+
+ function test_cleanCSS_scopeWithCommas() {
+ $this->config->set('Filter.ExtractStyleBlocks.Scope', '#foo, .bar');
+ $this->assertCleanCSS(
+ "p {\ntext-indent:1em;\n}",
+ "#foo p, .bar p {\ntext-indent:1em;\n}"
+ );
+ }
+
+ function test_cleanCSS_scopeAllWithCommas() {
+ $this->config->set('Filter.ExtractStyleBlocks.Scope', '#foo, .bar');
+ $this->assertCleanCSS(
+ "p, div {\ntext-indent:1em;\n}",
+ "#foo p, .bar p, #foo div, .bar div {\ntext-indent:1em;\n}"
+ );
+ }
+
+ function test_cleanCSS_scopeWithConflicts() {
+ $this->config->set('Filter.ExtractStyleBlocks.Scope', 'p');
+ $this->assertCleanCSS(
+"div {
+text-align:right;
+}
+
+p div {
+text-align:left;
+}",
+
+"p div {
+text-align:right;
+}
+
+p p div {
+text-align:left;
+}"
+ );
+ }
+
+ function test_removeComments() {
+ $this->assertCleanCSS(
+"<!--
+div {
+text-align:right;
+}
+-->",
+"div {
+text-align:right;
+}"
+ );
+ }
+
+ function test_atSelector() {
+ $this->assertCleanCSS(
+"{
+ b { text-align: center; }
+}",
+""
+ );
+ }
+
+ function test_selectorValidation() {
+ $this->assertCleanCSS(
+"&, & {
+text-align: center;
+}",
+""
+ );
+ $this->assertCleanCSS(
+"&, b {
+text-align:center;
+}",
+"b {
+text-align:center;
+}"
+ );
+ $this->assertCleanCSS(
+"& a #foo:hover.bar +b > i {
+text-align:center;
+}",
+"a #foo:hover.bar + b \\3E i {
+text-align:center;
+}"
+ );
+ $this->assertCleanCSS("doesnt-exist { text-align:center }", "");
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/GeneratorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/GeneratorTest.php
new file mode 100644
index 000000000..46b1dcf6b
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/GeneratorTest.php
@@ -0,0 +1,288 @@
+<?php
+
+class HTMLPurifier_GeneratorTest extends HTMLPurifier_Harness
+{
+
+ /**
+ * Entity lookup table to help for a few tests.
+ */
+ private $_entity_lookup;
+
+ public function __construct() {
+ parent::__construct();
+ $this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
+ }
+
+ public function setUp() {
+ parent::setUp();
+ $this->config->set('Output.Newline', "\n");
+ }
+
+ /**
+ * Creates a generator based on config and context member variables.
+ */
+ protected function createGenerator() {
+ return new HTMLPurifier_Generator($this->config, $this->context);
+ }
+
+ protected function assertGenerateFromToken($token, $html) {
+ $generator = $this->createGenerator();
+ $result = $generator->generateFromToken($token);
+ $this->assertIdentical($result, $html);
+ }
+
+ function test_generateFromToken_text() {
+ $this->assertGenerateFromToken(
+ new HTMLPurifier_Token_Text('Foobar.<>'),
+ 'Foobar.&lt;&gt;'
+ );
+ }
+
+ function test_generateFromToken_startWithAttr() {
+ $this->assertGenerateFromToken(
+ new HTMLPurifier_Token_Start('a',
+ array('href' => 'dyn?a=foo&b=bar')
+ ),
+ '<a href="dyn?a=foo&amp;b=bar">'
+ );
+ }
+
+ function test_generateFromToken_end() {
+ $this->assertGenerateFromToken(
+ new HTMLPurifier_Token_End('b'),
+ '</b>'
+ );
+ }
+
+ function test_generateFromToken_emptyWithAttr() {
+ $this->assertGenerateFromToken(
+ new HTMLPurifier_Token_Empty('br',
+ array('style' => 'font-family:"Courier New";')
+ ),
+ '<br style="font-family:&quot;Courier New&quot;;" />'
+ );
+ }
+
+ function test_generateFromToken_startNoAttr() {
+ $this->assertGenerateFromToken(
+ new HTMLPurifier_Token_Start('asdf'),
+ '<asdf>'
+ );
+ }
+
+ function test_generateFromToken_emptyNoAttr() {
+ $this->assertGenerateFromToken(
+ new HTMLPurifier_Token_Empty('br'),
+ '<br />'
+ );
+ }
+
+ function test_generateFromToken_error() {
+ $this->expectError('Cannot generate HTML from non-HTMLPurifier_Token object');
+ $this->assertGenerateFromToken( null, '' );
+ }
+
+ function test_generateFromToken_unicode() {
+ $theta_char = $this->_entity_lookup->table['theta'];
+ $this->assertGenerateFromToken(
+ new HTMLPurifier_Token_Text($theta_char),
+ $theta_char
+ );
+ }
+
+ function test_generateFromToken_backtick() {
+ $this->assertGenerateFromToken(
+ new HTMLPurifier_Token_Start('img', array('alt' => '`foo')),
+ '<img alt="`foo ">'
+ );
+ }
+
+ function test_generateFromToken_backtickDisabled() {
+ $this->config->set('Output.FixInnerHTML', false);
+ $this->assertGenerateFromToken(
+ new HTMLPurifier_Token_Start('img', array('alt' => '`')),
+ '<img alt="`">'
+ );
+ }
+
+ function test_generateFromToken_backtickNoChange() {
+ $this->assertGenerateFromToken(
+ new HTMLPurifier_Token_Start('img', array('alt' => '`foo` bar')),
+ '<img alt="`foo` bar">'
+ );
+ }
+
+ function assertGenerateAttributes($attr, $expect, $element = false) {
+ $generator = $this->createGenerator();
+ $result = $generator->generateAttributes($attr, $element);
+ $this->assertIdentical($result, $expect);
+ }
+
+ function test_generateAttributes_blank() {
+ $this->assertGenerateAttributes(array(), '');
+ }
+
+ function test_generateAttributes_basic() {
+ $this->assertGenerateAttributes(
+ array('href' => 'dyn?a=foo&b=bar'),
+ 'href="dyn?a=foo&amp;b=bar"'
+ );
+ }
+
+ function test_generateAttributes_doubleQuote() {
+ $this->assertGenerateAttributes(
+ array('style' => 'font-family:"Courier New";'),
+ 'style="font-family:&quot;Courier New&quot;;"'
+ );
+ }
+
+ function test_generateAttributes_singleQuote() {
+ $this->assertGenerateAttributes(
+ array('style' => 'font-family:\'Courier New\';'),
+ 'style="font-family:\'Courier New\';"'
+ );
+ }
+
+ function test_generateAttributes_multiple() {
+ $this->assertGenerateAttributes(
+ array('src' => 'picture.jpg', 'alt' => 'Short & interesting'),
+ 'src="picture.jpg" alt="Short &amp; interesting"'
+ );
+ }
+
+ function test_generateAttributes_specialChar() {
+ $theta_char = $this->_entity_lookup->table['theta'];
+ $this->assertGenerateAttributes(
+ array('title' => 'Theta is ' . $theta_char),
+ 'title="Theta is ' . $theta_char . '"'
+ );
+ }
+
+
+ function test_generateAttributes_minimized() {
+ $this->config->set('HTML.Doctype', 'HTML 4.01 Transitional');
+ $this->assertGenerateAttributes(
+ array('compact' => 'compact'), 'compact', 'menu'
+ );
+ }
+
+ function test_generateFromTokens() {
+
+ $this->assertGeneration(
+ array(
+ new HTMLPurifier_Token_Start('b'),
+ new HTMLPurifier_Token_Text('Foobar!'),
+ new HTMLPurifier_Token_End('b')
+ ),
+ '<b>Foobar!</b>'
+ );
+
+ }
+
+ protected function assertGeneration($tokens, $expect) {
+ $generator = new HTMLPurifier_Generator($this->config, $this->context);
+ $result = $generator->generateFromTokens($tokens);
+ $this->assertIdentical($expect, $result);
+ }
+
+ function test_generateFromTokens_Scripting() {
+ $this->assertGeneration(
+ array(
+ new HTMLPurifier_Token_Start('script'),
+ new HTMLPurifier_Token_Text('alert(3 < 5);'),
+ new HTMLPurifier_Token_End('script')
+ ),
+ "<script><!--//--><![CDATA[//><!--\nalert(3 < 5);\n//--><!]]></script>"
+ );
+ }
+
+ function test_generateFromTokens_Scripting_missingCloseTag() {
+ $this->assertGeneration(
+ array(
+ new HTMLPurifier_Token_Start('script'),
+ new HTMLPurifier_Token_Text('alert(3 < 5);'),
+ ),
+ "<script>alert(3 &lt; 5);"
+ );
+ }
+
+ function test_generateFromTokens_Scripting_doubleBlock() {
+ $this->assertGeneration(
+ array(
+ new HTMLPurifier_Token_Start('script'),
+ new HTMLPurifier_Token_Text('alert(3 < 5);'),
+ new HTMLPurifier_Token_Text('foo();'),
+ new HTMLPurifier_Token_End('script')
+ ),
+ "<script>alert(3 &lt; 5);foo();</script>"
+ );
+ }
+
+ function test_generateFromTokens_Scripting_disableWrapper() {
+ $this->config->set('Output.CommentScriptContents', false);
+ $this->assertGeneration(
+ array(
+ new HTMLPurifier_Token_Start('script'),
+ new HTMLPurifier_Token_Text('alert(3 < 5);'),
+ new HTMLPurifier_Token_End('script')
+ ),
+ "<script>alert(3 &lt; 5);</script>"
+ );
+ }
+
+ function test_generateFromTokens_XHTMLoff() {
+ $this->config->set('HTML.XHTML', false);
+
+ // omit trailing slash
+ $this->assertGeneration(
+ array( new HTMLPurifier_Token_Empty('br') ),
+ '<br>'
+ );
+
+ // there should be a test for attribute minimization, but it is
+ // impossible for something like that to happen due to our current
+ // definitions! fix it later
+
+ // namespaced attributes must be dropped
+ $this->assertGeneration(
+ array( new HTMLPurifier_Token_Start('p', array('xml:lang'=>'fr')) ),
+ '<p>'
+ );
+
+ }
+
+ function test_generateFromTokens_TidyFormat() {
+ // abort test if tidy isn't loaded
+ if (!extension_loaded('tidy')) return;
+
+ // just don't test; Tidy is exploding on me.
+ return;
+
+ $this->config->set('Core.TidyFormat', true);
+ $this->config->set('Output.Newline', "\n");
+
+ // nice wrapping please
+ $this->assertGeneration(
+ array(
+ new HTMLPurifier_Token_Start('div'),
+ new HTMLPurifier_Token_Text('Text'),
+ new HTMLPurifier_Token_End('div')
+ ),
+ "<div>\n Text\n</div>\n"
+ );
+
+ }
+
+ function test_generateFromTokens_sortAttr() {
+ $this->config->set('Output.SortAttr', true);
+
+ $this->assertGeneration(
+ array( new HTMLPurifier_Token_Start('p', array('b'=>'c', 'a'=>'d')) ),
+ '<p a="d" b="c">'
+ );
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLDefinitionTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLDefinitionTest.php
new file mode 100644
index 000000000..6640cb0d0
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLDefinitionTest.php
@@ -0,0 +1,355 @@
+<?php
+
+class HTMLPurifier_HTMLDefinitionTest extends HTMLPurifier_Harness
+{
+
+ function expectError($error = false, $message = '%s') {
+ // Because we're testing a definition, it's vital that the cache
+ // is turned off for tests that expect errors.
+ $this->config->set('Cache.DefinitionImpl', null);
+ parent::expectError($error);
+ }
+
+ function test_parseTinyMCEAllowedList() {
+
+ $def = new HTMLPurifier_HTMLDefinition();
+
+ // note: this is case-sensitive, but its config schema
+ // counterpart is not. This is generally a good thing for users,
+ // but it's a slight internal inconsistency
+
+ $this->assertEqual(
+ $def->parseTinyMCEAllowedList(''),
+ array(array(), array())
+ );
+
+ $this->assertEqual(
+ $def->parseTinyMCEAllowedList('a,b,c'),
+ array(array('a' => true, 'b' => true, 'c' => true), array())
+ );
+
+ $this->assertEqual(
+ $def->parseTinyMCEAllowedList('a[x|y|z]'),
+ array(array('a' => true), array('a.x' => true, 'a.y' => true, 'a.z' => true))
+ );
+
+ $this->assertEqual(
+ $def->parseTinyMCEAllowedList('*[id]'),
+ array(array(), array('*.id' => true))
+ );
+
+ $this->assertEqual(
+ $def->parseTinyMCEAllowedList('a[*]'),
+ array(array('a' => true), array('a.*' => true))
+ );
+
+ $this->assertEqual(
+ $def->parseTinyMCEAllowedList('span[style],strong,a[href|title]'),
+ array(array('span' => true, 'strong' => true, 'a' => true),
+ array('span.style' => true, 'a.href' => true, 'a.title' => true))
+ );
+
+ $this->assertEqual(
+ // alternate form:
+ $def->parseTinyMCEAllowedList(
+'span[style]
+strong
+a[href|title]
+'),
+ $val = array(array('span' => true, 'strong' => true, 'a' => true),
+ array('span.style' => true, 'a.href' => true, 'a.title' => true))
+ );
+
+ $this->assertEqual(
+ $def->parseTinyMCEAllowedList(' span [ style ], strong'."\n\t".'a[href | title]'),
+ $val
+ );
+
+ }
+
+ function test_Allowed() {
+
+ $config1 = HTMLPurifier_Config::create(array(
+ 'HTML.AllowedElements' => array('b', 'i', 'p', 'a'),
+ 'HTML.AllowedAttributes' => array('a@href', '*@id')
+ ));
+
+ $config2 = HTMLPurifier_Config::create(array(
+ 'HTML.Allowed' => 'b,i,p,a[href],*[id]'
+ ));
+
+ $this->assertEqual($config1->getHTMLDefinition(), $config2->getHTMLDefinition());
+
+ }
+
+ function assertPurification_AllowedElements_p() {
+ $this->assertPurification('<p><b>Jelly</b></p>', '<p>Jelly</p>');
+ }
+
+ function test_AllowedElements() {
+ $this->config->set('HTML.AllowedElements', 'p');
+ $this->assertPurification_AllowedElements_p();
+ }
+
+ function test_AllowedElements_multiple() {
+ $this->config->set('HTML.AllowedElements', 'p,div');
+ $this->assertPurification('<div><p><b>Jelly</b></p></div>', '<div><p>Jelly</p></div>');
+ }
+
+ function test_AllowedElements_invalidElement() {
+ $this->config->set('HTML.AllowedElements', 'obviously_invalid,p');
+ $this->expectError(new PatternExpectation("/Element 'obviously_invalid' is not supported/"));
+ $this->assertPurification_AllowedElements_p();
+ }
+
+ function test_AllowedElements_invalidElement_xssAttempt() {
+ $this->config->set('HTML.AllowedElements', '<script>,p');
+ $this->expectError(new PatternExpectation("/Element '&lt;script&gt;' is not supported/"));
+ $this->assertPurification_AllowedElements_p();
+ }
+
+ function test_AllowedElements_multipleInvalidElements() {
+ $this->config->set('HTML.AllowedElements', 'dr-wiggles,dr-pepper,p');
+ $this->expectError(new PatternExpectation("/Element 'dr-wiggles' is not supported/"));
+ $this->expectError(new PatternExpectation("/Element 'dr-pepper' is not supported/"));
+ $this->assertPurification_AllowedElements_p();
+ }
+
+ function assertPurification_AllowedAttributes_global_style() {
+ $this->assertPurification(
+ '<p style="font-weight:bold;" class="foo">Jelly</p><br style="clear:both;" />',
+ '<p style="font-weight:bold;">Jelly</p><br style="clear:both;" />');
+ }
+
+ function test_AllowedAttributes_global_preferredSyntax() {
+ $this->config->set('HTML.AllowedElements', array('p', 'br'));
+ $this->config->set('HTML.AllowedAttributes', 'style');
+ $this->assertPurification_AllowedAttributes_global_style();
+ }
+
+ function test_AllowedAttributes_global_verboseSyntax() {
+ $this->config->set('HTML.AllowedElements', array('p', 'br'));
+ $this->config->set('HTML.AllowedAttributes', '*@style');
+ $this->assertPurification_AllowedAttributes_global_style();
+ }
+
+ function test_AllowedAttributes_global_discouragedSyntax() {
+ // Emit errors eventually
+ $this->config->set('HTML.AllowedElements', array('p', 'br'));
+ $this->config->set('HTML.AllowedAttributes', '*.style');
+ $this->assertPurification_AllowedAttributes_global_style();
+ }
+
+ function assertPurification_AllowedAttributes_local_p_style() {
+ $this->assertPurification(
+ '<p style="font-weight:bold;" class="foo">Jelly</p><br style="clear:both;" />',
+ '<p style="font-weight:bold;">Jelly</p><br />');
+ }
+
+ function test_AllowedAttributes_local_preferredSyntax() {
+ $this->config->set('HTML.AllowedElements', array('p', 'br'));
+ $this->config->set('HTML.AllowedAttributes', 'p@style');
+ $this->assertPurification_AllowedAttributes_local_p_style();
+ }
+
+ function test_AllowedAttributes_local_discouragedSyntax() {
+ $this->config->set('HTML.AllowedElements', array('p', 'br'));
+ $this->config->set('HTML.AllowedAttributes', 'p.style');
+ $this->assertPurification_AllowedAttributes_local_p_style();
+ }
+
+ function test_AllowedAttributes_multiple() {
+ $this->config->set('HTML.AllowedElements', array('p', 'br'));
+ $this->config->set('HTML.AllowedAttributes', 'p@style,br@class,title');
+ $this->assertPurification(
+ '<p style="font-weight:bold;" class="foo" title="foo">Jelly</p><br style="clear:both;" class="foo" title="foo" />',
+ '<p style="font-weight:bold;" title="foo">Jelly</p><br class="foo" title="foo" />'
+ );
+ }
+
+ function test_AllowedAttributes_local_invalidAttribute() {
+ $this->config->set('HTML.AllowedElements', array('p', 'br'));
+ $this->config->set('HTML.AllowedAttributes', array('p@style', 'p@<foo>'));
+ $this->expectError(new PatternExpectation("/Attribute '&lt;foo&gt;' in element 'p' not supported/"));
+ $this->assertPurification_AllowedAttributes_local_p_style();
+ }
+
+ function test_AllowedAttributes_global_invalidAttribute() {
+ $this->config->set('HTML.AllowedElements', array('p', 'br'));
+ $this->config->set('HTML.AllowedAttributes', array('style', '<foo>'));
+ $this->expectError(new PatternExpectation("/Global attribute '&lt;foo&gt;' is not supported in any elements/"));
+ $this->assertPurification_AllowedAttributes_global_style();
+ }
+
+ function test_AllowedAttributes_local_invalidAttributeDueToMissingElement() {
+ $this->config->set('HTML.AllowedElements', array('p', 'br'));
+ $this->config->set('HTML.AllowedAttributes', 'p.style,foo.style');
+ $this->expectError(new PatternExpectation("/Cannot allow attribute 'style' if element 'foo' is not allowed\/supported/"));
+ $this->assertPurification_AllowedAttributes_local_p_style();
+ }
+
+ function test_AllowedAttributes_duplicate() {
+ $this->config->set('HTML.AllowedElements', array('p', 'br'));
+ $this->config->set('HTML.AllowedAttributes', 'p.style,p@style');
+ $this->assertPurification_AllowedAttributes_local_p_style();
+ }
+
+ function test_AllowedAttributes_multipleErrors() {
+ $this->config->set('HTML.AllowedElements', array('p', 'br'));
+ $this->config->set('HTML.AllowedAttributes', 'p.style,foo.style,<foo>');
+ $this->expectError(new PatternExpectation("/Cannot allow attribute 'style' if element 'foo' is not allowed\/supported/"));
+ $this->expectError(new PatternExpectation("/Global attribute '&lt;foo&gt;' is not supported in any elements/"));
+ $this->assertPurification_AllowedAttributes_local_p_style();
+ }
+
+ function test_ForbiddenElements() {
+ $this->config->set('HTML.ForbiddenElements', 'b');
+ $this->assertPurification('<b>b</b><i>i</i>', 'b<i>i</i>');
+ }
+
+ function test_ForbiddenElements_invalidElement() {
+ $this->config->set('HTML.ForbiddenElements', 'obviously_incorrect');
+ // no error!
+ $this->assertPurification('<i>i</i>');
+ }
+
+ function assertPurification_ForbiddenAttributes_b_style() {
+ $this->assertPurification(
+ '<b style="float:left;">b</b><i style="float:left;">i</i>',
+ '<b>b</b><i style="float:left;">i</i>');
+ }
+
+ function test_ForbiddenAttributes() {
+ $this->config->set('HTML.ForbiddenAttributes', 'b@style');
+ $this->assertPurification_ForbiddenAttributes_b_style();
+ }
+
+ function test_ForbiddenAttributes_incorrectSyntax() {
+ $this->config->set('HTML.ForbiddenAttributes', 'b.style');
+ $this->expectError("Error with b.style: tag.attr syntax not supported for HTML.ForbiddenAttributes; use tag@attr instead");
+ $this->assertPurification('<b style="float:left;">Test</b>');
+ }
+
+ function test_ForbiddenAttributes_incorrectGlobalSyntax() {
+ $this->config->set('HTML.ForbiddenAttributes', '*.style');
+ $this->expectError("Error with *.style: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead");
+ $this->assertPurification('<b style="float:left;">Test</b>');
+ }
+
+ function assertPurification_ForbiddenAttributes_style() {
+ $this->assertPurification(
+ '<b class="foo" style="float:left;">b</b><i style="float:left;">i</i>',
+ '<b class="foo">b</b><i>i</i>');
+ }
+
+ function test_ForbiddenAttributes_global() {
+ $this->config->set('HTML.ForbiddenAttributes', 'style');
+ $this->assertPurification_ForbiddenAttributes_style();
+ }
+
+ function test_ForbiddenAttributes_globalVerboseFormat() {
+ $this->config->set('HTML.ForbiddenAttributes', '*@style');
+ $this->assertPurification_ForbiddenAttributes_style();
+ }
+
+ function test_addAttribute() {
+
+ $config = HTMLPurifier_Config::createDefault();
+ $def = $config->getHTMLDefinition(true);
+ $def->addAttribute('span', 'custom', 'Enum#attribute');
+
+ $purifier = new HTMLPurifier($config);
+ $input = '<span custom="attribute">Custom!</span>';
+ $output = $purifier->purify($input);
+ $this->assertIdentical($input, $output);
+
+ }
+
+ function test_addAttribute_multiple() {
+
+ $config = HTMLPurifier_Config::createDefault();
+ $def = $config->getHTMLDefinition(true);
+ $def->addAttribute('span', 'custom', 'Enum#attribute');
+ $def->addAttribute('span', 'foo', 'Text');
+
+ $purifier = new HTMLPurifier($config);
+ $input = '<span custom="attribute" foo="asdf">Custom!</span>';
+ $output = $purifier->purify($input);
+ $this->assertIdentical($input, $output);
+
+ }
+
+ function test_addElement() {
+
+ $config = HTMLPurifier_Config::createDefault();
+ $def = $config->getHTMLDefinition(true);
+ $def->addElement('marquee', 'Inline', 'Inline', 'Common', array('width' => 'Length'));
+
+ $purifier = new HTMLPurifier($config);
+ $input = '<span><marquee width="50">Foobar</marquee></span>';
+ $output = $purifier->purify($input);
+ $this->assertIdentical($input, $output);
+
+ }
+
+ function test_injector() {
+ generate_mock_once('HTMLPurifier_Injector');
+ $injector = new HTMLPurifier_InjectorMock();
+ $injector->name = 'MyInjector';
+ $injector->setReturnValue('checkNeeded', false);
+
+ $module = $this->config->getHTMLDefinition(true)->getAnonymousModule();
+ $module->info_injector[] = $injector;
+
+ $this->assertIdentical($this->config->getHTMLDefinition()->info_injector,
+ array(
+ 'MyInjector' => $injector,
+ )
+ );
+ }
+
+ function test_injectorMissingNeeded() {
+ generate_mock_once('HTMLPurifier_Injector');
+ $injector = new HTMLPurifier_InjectorMock();
+ $injector->name = 'MyInjector';
+ $injector->setReturnValue('checkNeeded', 'a');
+
+ $module = $this->config->getHTMLDefinition(true)->getAnonymousModule();
+ $module->info_injector[] = $injector;
+
+ $this->assertIdentical($this->config->getHTMLDefinition()->info_injector,
+ array()
+ );
+ }
+
+ function test_injectorIntegration() {
+ $module = $this->config->getHTMLDefinition(true)->getAnonymousModule();
+ $module->info_injector[] = 'Linkify';
+
+ $this->assertIdentical(
+ $this->config->getHTMLDefinition()->info_injector,
+ array('Linkify' => new HTMLPurifier_Injector_Linkify())
+ );
+ }
+
+ function test_injectorIntegrationFail() {
+ $this->config->set('HTML.Allowed', 'p');
+
+ $module = $this->config->getHTMLDefinition(true)->getAnonymousModule();
+ $module->info_injector[] = 'Linkify';
+
+ $this->assertIdentical(
+ $this->config->getHTMLDefinition()->info_injector,
+ array()
+ );
+ }
+
+ function test_notAllowedRequiredAttributeError() {
+ $this->expectError("Required attribute 'src' in element 'img' was not allowed, which means 'img' will not be allowed either");
+ $this->config->set('HTML.Allowed', 'img[alt]');
+ $this->config->getHTMLDefinition();
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/FormsTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/FormsTest.php
new file mode 100644
index 000000000..5bc4c99c3
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/FormsTest.php
@@ -0,0 +1,155 @@
+<?php
+
+class HTMLPurifier_HTMLModule_FormsTest extends HTMLPurifier_HTMLModuleHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->config->set('HTML.Trusted', true);
+ $this->config->set('Attr.EnableID', true);
+ }
+
+ function testBasicUse() {
+ $this->config->set('HTML.Doctype', 'HTML 4.01 Strict');
+ $this->assertResult( // need support for label for later
+ '
+<form action="http://somesite.com/prog/adduser" method="post">
+ <p>
+ <label>First name: </label>
+ <input type="text" id="firstname" /><br />
+ <label>Last name: </label>
+ <input type="text" id="lastname" /><br />
+ <label>email: </label>
+ <input type="text" id="email" /><br />
+ <input type="radio" name="sex" value="Male" /> Male<br />
+ <input type="radio" name="sex" value="Female" /> Female<br />
+ <input type="submit" value="Send" /> <input type="reset" />
+ </p>
+</form>'
+ );
+ }
+
+ function testSelectOption() {
+ $this->config->set('HTML.Doctype', 'HTML 4.01 Strict');
+ $this->assertResult('
+<form action="http://somesite.com/prog/component-select" method="post">
+ <p>
+ <select multiple="multiple" size="4" name="component-select">
+ <option selected="selected" value="Component_1_a">Component_1</option>
+ <option selected="selected" value="Component_1_b">Component_2</option>
+ <option>Component_3</option>
+ <option>Component_4</option>
+ <option>Component_5</option>
+ <option>Component_6</option>
+ <option>Component_7</option>
+ </select>
+ <input type="submit" value="Send" /><input type="reset" />
+ </p>
+</form>
+ ');
+ }
+
+ function testSelectOptgroup() {
+ $this->config->set('HTML.Doctype', 'HTML 4.01 Strict');
+ $this->assertResult('
+<form action="http://somesite.com/prog/someprog" method="post">
+<p>
+ <select name="ComOS">
+ <option selected="selected" label="none" value="none">None</option>
+ <optgroup label="PortMaster 3">
+ <option label="3.7.1" value="pm3_3.7.1">PortMaster 3 with ComOS 3.7.1</option>
+ <option label="3.7" value="pm3_3.7">PortMaster 3 with ComOS 3.7</option>
+ <option label="3.5" value="pm3_3.5">PortMaster 3 with ComOS 3.5</option>
+ </optgroup>
+ <optgroup label="PortMaster 2">
+ <option label="3.7" value="pm2_3.7">PortMaster 2 with ComOS 3.7</option>
+ <option label="3.5" value="pm2_3.5">PortMaster 2 with ComOS 3.5</option>
+ </optgroup>
+ <optgroup label="IRX">
+ <option label="3.7R" value="IRX_3.7R">IRX with ComOS 3.7R</option>
+ <option label="3.5R" value="IRX_3.5R">IRX with ComOS 3.5R</option>
+ </optgroup>
+ </select>
+</p>
+</form>
+ ');
+ }
+
+ function testTextarea() {
+ $this->config->set('HTML.Doctype', 'HTML 4.01 Strict');
+ $this->assertResult('
+<form action="http://somesite.com/prog/text-read" method="post">
+ <p>
+ <textarea name="thetext" rows="20" cols="80">
+ First line of initial text.
+ Second line of initial text.
+ </textarea>
+ <input type="submit" value="Send" /><input type="reset" />
+ </p>
+</form>
+ ');
+ }
+
+ // label tests omitted
+
+ function testFieldset() {
+ $this->config->set('HTML.Doctype', 'HTML 4.01 Strict');
+ $this->assertResult('
+<form action="..." method="post">
+ <fieldset>
+ <legend>Personal Information</legend>
+ Last Name: <input name="personal_lastname" type="text" tabindex="1" />
+ First Name: <input name="personal_firstname" type="text" tabindex="2" />
+ Address: <input name="personal_address" type="text" tabindex="3" />
+ ...more personal information...
+ </fieldset>
+ <fieldset>
+ <legend>Medical History</legend>
+ <input name="history_illness" type="checkbox" value="Smallpox" tabindex="20" />Smallpox
+ <input name="history_illness" type="checkbox" value="Mumps" tabindex="21" /> Mumps
+ <input name="history_illness" type="checkbox" value="Dizziness" tabindex="22" /> Dizziness
+ <input name="history_illness" type="checkbox" value="Sneezing" tabindex="23" /> Sneezing
+ ...more medical history...
+ </fieldset>
+ <fieldset>
+ <legend>Current Medication</legend>
+ Are you currently taking any medication?
+ <input name="medication_now" type="radio" value="Yes" tabindex="35" />Yes
+ <input name="medication_now" type="radio" value="No" tabindex="35" />No
+
+ If you are currently taking medication, please indicate
+ it in the space below:
+ <textarea name="current_medication" rows="20" cols="50" tabindex="40"></textarea>
+ </fieldset>
+</form>
+ ');
+ }
+
+ function testInputTransform() {
+ $this->config->set('HTML.Doctype', 'XHTML 1.0 Strict');
+ $this->assertResult('<input type="checkbox" />', '<input type="checkbox" value="" />');
+ }
+
+ function testTextareaTransform() {
+ $this->config->set('HTML.Doctype', 'HTML 4.01 Strict');
+ $this->assertResult('<textarea></textarea>', '<textarea cols="22" rows="3"></textarea>');
+ }
+
+ function testTextInFieldset() {
+ $this->config->set('HTML.Doctype', 'HTML 4.01 Strict');
+ $this->assertResult('<fieldset> <legend></legend>foo</fieldset>');
+ }
+
+ function testStrict() {
+ $this->config->set('HTML.Doctype', 'HTML 4.01 Strict');
+ $this->assertResult('<form action=""></form>', '');
+ }
+
+ function testLegacy() {
+ $this->assertResult('<form action=""></form>');
+ $this->assertResult('<form action=""><input align="left" /></form>');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ImageTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ImageTest.php
new file mode 100644
index 000000000..30e36f1d1
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ImageTest.php
@@ -0,0 +1,55 @@
+<?php
+
+class HTMLPurifier_HTMLModule_ImageTest extends HTMLPurifier_HTMLModuleHarness
+{
+
+
+ function testNormal() {
+ $this->assertResult('<img height="40" width="40" src="" alt="" />');
+ }
+
+ function testLengthTooLarge() {
+ $this->assertResult(
+ '<img height="40000" width="40000" src="" alt="" />',
+ '<img height="1200" width="1200" src="" alt="" />'
+ );
+ }
+
+ function testLengthPercentage() {
+ $this->assertResult(
+ '<img height="100%" width="100%" src="" alt="" />',
+ '<img src="" alt="" />'
+ );
+ }
+
+ function testLengthCustomMax() {
+ $this->config->set('HTML.MaxImgLength', 20);
+ $this->assertResult(
+ '<img height="30" width="30" src="" alt="" />',
+ '<img height="20" width="20" src="" alt="" />'
+ );
+ }
+
+ function testLengthCrashFixDisabled() {
+ $this->config->set('HTML.MaxImgLength', null);
+ $this->assertResult(
+ '<img height="100%" width="100%" src="" alt="" />'
+ );
+ $this->assertResult(
+ '<img height="40000" width="40000" src="" alt="" />'
+ );
+ }
+
+ function testLengthTrusted() {
+ $this->config->set('HTML.Trusted', true);
+ $this->assertResult(
+ '<img height="100%" width="100%" src="" alt="" />'
+ );
+ $this->assertResult(
+ '<img height="40000" width="40000" src="" alt="" />'
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/NameTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/NameTest.php
new file mode 100644
index 000000000..9152710bd
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/NameTest.php
@@ -0,0 +1,32 @@
+<?php
+
+class HTMLPurifier_HTMLModule_NameTest extends HTMLPurifier_HTMLModuleHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ }
+
+ function testBasicUse() {
+ $this->config->set('Attr.EnableID', true);
+ $this->assertResult(
+ '<a name="foo">bar</a>'
+ );
+ }
+
+ function testCDATA() {
+ $this->config->set('HTML.Attr.Name.UseCDATA', true);
+ $this->assertResult(
+ '<a name="2">Baz</a><a name="2">Bar</a>'
+ );
+ }
+
+ function testCDATAWithHeavyTidy() {
+ $this->config->set('HTML.Attr.Name.UseCDATA', true);
+ $this->config->set('HTML.TidyLevel', 'heavy');
+ $this->assertResult('<a name="2">Baz</a>');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/NofollowTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/NofollowTest.php
new file mode 100644
index 000000000..43084de3a
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/NofollowTest.php
@@ -0,0 +1,26 @@
+<?php
+
+class HTMLPurifier_HTMLModule_NofollowTest extends HTMLPurifier_HTMLModuleHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->config->set('HTML.Nofollow', true);
+ }
+
+ function testNofollow() {
+ $this->assertResult(
+ '<a href="http://google.com">a</a><a href="/local">b</a><a href="mailto:foo@example.com">c</a>',
+ '<a href="http://google.com" rel="nofollow">a</a><a href="/local">b</a><a href="mailto:foo@example.com">c</a>'
+ );
+ }
+
+ function testNofollowDupe() {
+ $this->assertResult(
+ '<a href="http://google.com" rel="nofollow">a</a><a href="/local">b</a><a href="mailto:foo@example.com">c</a>'
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ObjectTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ObjectTest.php
new file mode 100644
index 000000000..f824d6021
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ObjectTest.php
@@ -0,0 +1,38 @@
+<?php
+
+class HTMLPurifier_HTMLModule_ObjectTest extends HTMLPurifier_HTMLModuleHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->config->set('HTML.Trusted', true);
+ }
+
+ function testDefaultRemoval() {
+ $this->config->set('HTML.Trusted', false);
+ $this->assertResult(
+ '<object></object>', ''
+ );
+ }
+
+ function testMinimal() {
+ $this->assertResult('<object></object>');
+ }
+
+ function testStandardUseCase() {
+ $this->assertResult(
+'<object type="video/x-ms-wmv" data="http://domain.com/video.wmv" width="320" height="256">
+<param name="src" value="http://domain.com/video.wmv" />
+<param name="autostart" value="false" />
+<param name="controller" value="true" />
+<param name="pluginurl" value="http://www.microsoft.com/Windows/MediaPlayer/" />
+<a href="http://www.microsoft.com/Windows/MediaPlayer/">Windows Media player required</a>
+</object>'
+ );
+ }
+
+ // more test-cases?
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ProprietaryTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ProprietaryTest.php
new file mode 100644
index 000000000..21ebe8a5b
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ProprietaryTest.php
@@ -0,0 +1,30 @@
+<?php
+
+class HTMLPurifier_HTMLModule_ProprietaryTest extends HTMLPurifier_HTMLModuleHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->config->set('HTML.Proprietary', true);
+ }
+
+ function testMarquee() {
+ $this->assertResult(
+ '<span><marquee
+ width="20%"
+ height="34"
+ direction="left"
+ behavior="alternate"
+ scrolldelay="3"
+ scrollamount="5"
+ loop="4"
+ bgcolor="#FF0000"
+ hspace="5"
+ vspace="3"
+ ><div>Block</div><span>Inline</span>Text</marquee></span>'
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/RubyTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/RubyTest.php
new file mode 100644
index 000000000..23a1400d0
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/RubyTest.php
@@ -0,0 +1,55 @@
+<?php
+
+class HTMLPurifier_HTMLModule_RubyTest extends HTMLPurifier_HTMLModuleHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->config->set('HTML.Doctype', 'XHTML 1.1');
+ }
+
+ function testBasicUse() {
+ $this->assertResult(
+ '<ruby><rb>WWW</rb><rt>World Wide Web</rt></ruby>'
+ );
+ }
+
+ function testRPUse() {
+ $this->assertResult(
+ '<ruby><rb>WWW</rb><rp>(</rp><rt>World Wide Web</rt><rp>)</rp></ruby>'
+ );
+ }
+
+ function testComplexUse() {
+ $this->assertResult(
+'<ruby>
+ <rbc>
+ <rb>10</rb>
+ <rb>31</rb>
+ <rb>2002</rb>
+ </rbc>
+ <rtc>
+ <rt>Month</rt>
+ <rt>Day</rt>
+ <rt>Year</rt>
+ </rtc>
+ <rtc>
+ <rt rbspan="3">Expiration Date</rt>
+ </rtc>
+</ruby>'
+ );
+
+ /* not implemented
+ function testBackwardsCompat() {
+ $this->assertResult(
+ '<ruby>A<rp>(</rp><rt>aaa</rt><rp>)</rp></ruby>',
+ '<ruby><rb>A</rb><rp>(</rp><rt>aaa</rt><rp>)</rp></ruby>'
+ );
+ }
+ */
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/SafeEmbedTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/SafeEmbedTest.php
new file mode 100644
index 000000000..779d34857
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/SafeEmbedTest.php
@@ -0,0 +1,41 @@
+<?php
+
+class HTMLPurifier_HTMLModule_SafeEmbedTest extends HTMLPurifier_HTMLModuleHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $def = $this->config->getHTMLDefinition(true);
+ $def->manager->addModule('SafeEmbed');
+ }
+
+ function testMinimal() {
+ $this->assertResult(
+ '<embed src="http://www.youtube.com/v/RVtEQxH7PWA&amp;hl=en" />',
+ '<embed src="http://www.youtube.com/v/RVtEQxH7PWA&amp;hl=en" allowscriptaccess="never" allownetworking="internal" type="application/x-shockwave-flash" />'
+ );
+ }
+
+ function testYouTube() {
+ $this->assertResult(
+ '<embed src="http://www.youtube.com/v/RVtEQxH7PWA&amp;hl=en" type="application/x-shockwave-flash" width="425" height="344"></embed>',
+ '<embed src="http://www.youtube.com/v/RVtEQxH7PWA&amp;hl=en" type="application/x-shockwave-flash" width="425" height="344" allowscriptaccess="never" allownetworking="internal" />'
+ );
+ }
+
+ function testMalicious() {
+ $this->assertResult(
+ '<embed src="http://example.com/bad.swf" type="application/x-shockwave-flash" width="9999999" height="3499994" allowscriptaccess="always" allownetworking="always" />',
+ '<embed src="http://example.com/bad.swf" type="application/x-shockwave-flash" width="1200" height="1200" allowscriptaccess="never" allownetworking="internal" />'
+ );
+ }
+
+ function testFull() {
+ $this->assertResult(
+ '<b><embed src="http://www.youtube.com/v/RVtEQxH7PWA&amp;hl=en" type="application/x-shockwave-flash" width="24" height="23" allowscriptaccess="never" allownetworking="internal" wmode="window" /></b>'
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/SafeObjectTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/SafeObjectTest.php
new file mode 100644
index 000000000..d8a23d50f
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/SafeObjectTest.php
@@ -0,0 +1,49 @@
+<?php
+
+class HTMLPurifier_HTMLModule_SafeObjectTest extends HTMLPurifier_HTMLModuleHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->config->set('HTML.DefinitionID', 'HTMLPurifier_HTMLModule_SafeObjectTest');
+ $this->config->set('HTML.SafeObject', true);
+ }
+
+ function testMinimal() {
+ $this->assertResult(
+ '<object></object>',
+ '<object type="application/x-shockwave-flash"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object>'
+ );
+ }
+
+ function testYouTube() {
+ // embed is purposely removed
+ $this->assertResult(
+ '<object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/RVtEQxH7PWA&hl=en"></param><embed src="http://www.youtube.com/v/RVtEQxH7PWA&hl=en" type="application/x-shockwave-flash" width="425" height="344"></embed></object>',
+ '<object width="425" height="344" data="http://www.youtube.com/v/RVtEQxH7PWA&amp;hl=en" type="application/x-shockwave-flash"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><param name="movie" value="http://www.youtube.com/v/RVtEQxH7PWA&amp;hl=en" /></object>'
+ );
+ }
+
+ function testMalicious() {
+ $this->assertResult(
+ '<object width="9999999" height="9999999"><param name="allowScriptAccess" value="always" /><param name="movie" value="http://example.com/attack.swf" /></object>',
+ '<object width="1200" height="1200" data="http://example.com/attack.swf" type="application/x-shockwave-flash"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><param name="movie" value="http://example.com/attack.swf" /></object>'
+ );
+ }
+
+ function testFull() {
+ $this->assertResult(
+ '<b><object width="425" height="344" type="application/x-shockwave-flash" data="Foobar"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><param name="flashvars" value="foobarbaz=bally" /><param name="movie" value="http://www.youtube.com/v/RVtEQxH7PWA&amp;hl=en" /><param name="wmode" value="window" /></object></b>'
+ );
+ }
+
+ function testFullScreen() {
+ $this->config->set('HTML.FlashAllowFullScreen', true);
+ $this->assertResult(
+ '<b><object width="425" height="344" type="application/x-shockwave-flash" data="Foobar"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><param name="flashvars" value="foobarbaz=bally" /><param name="movie" value="http://www.youtube.com/v/RVtEQxH7PWA&amp;hl=en" /><param name="wmode" value="window" /><param name="allowFullScreen" value="true" /></object></b>'
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ScriptingTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ScriptingTest.php
new file mode 100644
index 000000000..c844a4776
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ScriptingTest.php
@@ -0,0 +1,55 @@
+<?php
+
+class HTMLPurifier_HTMLModule_ScriptingTest extends HTMLPurifier_HTMLModuleHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->config->set('HTML.Trusted', true);
+ $this->config->set('Output.CommentScriptContents', false);
+ }
+
+ function testDefaultRemoval() {
+ $this->config->set('HTML.Trusted', false);
+ $this->assertResult(
+ '<script type="text/javascript">foo();</script>', ''
+ );
+ }
+
+ function testPreserve() {
+ $this->assertResult(
+ '<script type="text/javascript">foo();</script>'
+ );
+ }
+
+ function testCDATAEnclosure() {
+ $this->assertResult(
+'<script type="text/javascript">//<![CDATA[
+alert("<This is compatible with XHTML>");
+//]]></script>'
+ );
+ }
+
+ function testAllAttributes() {
+ $this->assertResult(
+ '<script
+ defer="defer"
+ src="test.js"
+ type="text/javascript"
+ >PCDATA</script>'
+ );
+ }
+
+ function testUnsupportedAttributes() {
+ $this->assertResult(
+ '<script
+ type="text/javascript"
+ charset="utf-8"
+ >PCDATA</script>',
+ '<script type="text/javascript">PCDATA</script>'
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/TargetBlankTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/TargetBlankTest.php
new file mode 100644
index 000000000..a757fecb3
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/TargetBlankTest.php
@@ -0,0 +1,20 @@
+<?php
+
+class HTMLPurifier_HTMLModule_TargetBlankTest extends HTMLPurifier_HTMLModuleHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->config->set('HTML.TargetBlank', true);
+ }
+
+ function testTargetBlank() {
+ $this->assertResult(
+ '<a href="http://google.com">a</a><a href="/local">b</a><a href="mailto:foo@example.com">c</a>',
+ '<a href="http://google.com" target="blank">a</a><a href="/local">b</a><a href="mailto:foo@example.com">c</a>'
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/TidyTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/TidyTest.php
new file mode 100644
index 000000000..e80aeb16d
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/TidyTest.php
@@ -0,0 +1,224 @@
+<?php
+
+Mock::generatePartial(
+ 'HTMLPurifier_HTMLModule_Tidy',
+ 'HTMLPurifier_HTMLModule_Tidy_TestForConstruct',
+ array('makeFixes', 'makeFixesForLevel', 'populate')
+);
+
+class HTMLPurifier_HTMLModule_TidyTest extends HTMLPurifier_Harness
+{
+
+ function test_getFixesForLevel() {
+
+ $module = new HTMLPurifier_HTMLModule_Tidy();
+ $module->fixesForLevel['light'][] = 'light-fix';
+ $module->fixesForLevel['medium'][] = 'medium-fix';
+ $module->fixesForLevel['heavy'][] = 'heavy-fix';
+
+ $this->assertIdentical(
+ array(),
+ $module->getFixesForLevel('none')
+ );
+ $this->assertIdentical(
+ array('light-fix' => true),
+ $module->getFixesForLevel('light')
+ );
+ $this->assertIdentical(
+ array('light-fix' => true, 'medium-fix' => true),
+ $module->getFixesForLevel('medium')
+ );
+ $this->assertIdentical(
+ array('light-fix' => true, 'medium-fix' => true, 'heavy-fix' => true),
+ $module->getFixesForLevel('heavy')
+ );
+
+ $this->expectError('Tidy level turbo not recognized');
+ $module->getFixesForLevel('turbo');
+
+ }
+
+ function test_setup() {
+
+ $i = 0; // counter, helps us isolate expectations
+
+ // initialize partial mock
+ $module = new HTMLPurifier_HTMLModule_Tidy_TestForConstruct();
+ $module->fixesForLevel['light'] = array('light-fix-1', 'light-fix-2');
+ $module->fixesForLevel['medium'] = array('medium-fix-1', 'medium-fix-2');
+ $module->fixesForLevel['heavy'] = array('heavy-fix-1', 'heavy-fix-2');
+
+ $j = 0;
+ $fixes = array(
+ 'light-fix-1' => $lf1 = $j++,
+ 'light-fix-2' => $lf2 = $j++,
+ 'medium-fix-1' => $mf1 = $j++,
+ 'medium-fix-2' => $mf2 = $j++,
+ 'heavy-fix-1' => $hf1 = $j++,
+ 'heavy-fix-2' => $hf2 = $j++
+ );
+ $module->setReturnValue('makeFixes', $fixes);
+
+ $config = HTMLPurifier_Config::create(array(
+ 'HTML.TidyLevel' => 'none'
+ ));
+ $module->expectAt($i++, 'populate', array(array()));
+ $module->setup($config);
+
+ // basic levels
+
+ $config = HTMLPurifier_Config::create(array(
+ 'HTML.TidyLevel' => 'light'
+ ));
+ $module->expectAt($i++, 'populate', array(array(
+ 'light-fix-1' => $lf1,
+ 'light-fix-2' => $lf2
+ )));
+ $module->setup($config);
+
+ $config = HTMLPurifier_Config::create(array(
+ 'HTML.TidyLevel' => 'heavy'
+ ));
+ $module->expectAt($i++, 'populate', array(array(
+ 'light-fix-1' => $lf1,
+ 'light-fix-2' => $lf2,
+ 'medium-fix-1' => $mf1,
+ 'medium-fix-2' => $mf2,
+ 'heavy-fix-1' => $hf1,
+ 'heavy-fix-2' => $hf2
+ )));
+ $module->setup($config);
+
+ // fine grained tuning
+
+ $config = HTMLPurifier_Config::create(array(
+ 'HTML.TidyLevel' => 'none',
+ 'HTML.TidyAdd' => array('light-fix-1', 'medium-fix-1')
+ ));
+ $module->expectAt($i++, 'populate', array(array(
+ 'light-fix-1' => $lf1,
+ 'medium-fix-1' => $mf1
+ )));
+ $module->setup($config);
+
+ $config = HTMLPurifier_Config::create(array(
+ 'HTML.TidyLevel' => 'medium',
+ 'HTML.TidyRemove' => array('light-fix-1', 'medium-fix-1')
+ ));
+ $module->expectAt($i++, 'populate', array(array(
+ 'light-fix-2' => $lf2,
+ 'medium-fix-2' => $mf2
+ )));
+ $module->setup($config);
+
+ }
+
+ function test_makeFixesForLevel() {
+
+ $module = new HTMLPurifier_HTMLModule_Tidy();
+ $module->defaultLevel = 'heavy';
+
+ $module->makeFixesForLevel(array(
+ 'fix-1' => 0,
+ 'fix-2' => 1,
+ 'fix-3' => 2
+ ));
+
+ $this->assertIdentical($module->fixesForLevel['heavy'], array('fix-1', 'fix-2', 'fix-3'));
+ $this->assertIdentical($module->fixesForLevel['medium'], array());
+ $this->assertIdentical($module->fixesForLevel['light'], array());
+
+ }
+ function test_makeFixesForLevel_undefinedLevel() {
+
+ $module = new HTMLPurifier_HTMLModule_Tidy();
+ $module->defaultLevel = 'bananas';
+
+ $this->expectError('Default level bananas does not exist');
+
+ $module->makeFixesForLevel(array(
+ 'fix-1' => 0
+ ));
+
+ }
+
+ function test_getFixType() {
+
+ // syntax needs documenting
+
+ $module = new HTMLPurifier_HTMLModule_Tidy();
+
+ $this->assertIdentical(
+ $module->getFixType('a'),
+ array('tag_transform', array('element' => 'a'))
+ );
+
+ $this->assertIdentical(
+ $module->getFixType('a@href'),
+ $reuse = array('attr_transform_pre', array('element' => 'a', 'attr' => 'href'))
+ );
+
+ $this->assertIdentical(
+ $module->getFixType('a@href#pre'),
+ $reuse
+ );
+
+ $this->assertIdentical(
+ $module->getFixType('a@href#post'),
+ array('attr_transform_post', array('element' => 'a', 'attr' => 'href'))
+ );
+
+ $this->assertIdentical(
+ $module->getFixType('xml:foo@xml:bar'),
+ array('attr_transform_pre', array('element' => 'xml:foo', 'attr' => 'xml:bar'))
+ );
+
+ $this->assertIdentical(
+ $module->getFixType('blockquote#child'),
+ array('child', array('element' => 'blockquote'))
+ );
+
+ $this->assertIdentical(
+ $module->getFixType('@lang'),
+ array('attr_transform_pre', array('attr' => 'lang'))
+ );
+
+ $this->assertIdentical(
+ $module->getFixType('@lang#post'),
+ array('attr_transform_post', array('attr' => 'lang'))
+ );
+
+ }
+
+ function test_populate() {
+
+ $i = 0;
+
+ $module = new HTMLPurifier_HTMLModule_Tidy();
+ $module->populate(array(
+ 'element' => $element = $i++,
+ 'element@attr' => $attr = $i++,
+ 'element@attr#post' => $attr_post = $i++,
+ 'element#child' => $child = $i++,
+ 'element#content_model_type' => $content_model_type = $i++,
+ '@attr' => $global_attr = $i++,
+ '@attr#post' => $global_attr_post = $i++
+ ));
+
+ $module2 = new HTMLPurifier_HTMLModule_Tidy();
+ $e = $module2->addBlankElement('element');
+ $e->attr_transform_pre['attr'] = $attr;
+ $e->attr_transform_post['attr'] = $attr_post;
+ $e->child = $child;
+ $e->content_model_type = $content_model_type;
+ $module2->info_tag_transform['element'] = $element;
+ $module2->info_attr_transform_pre['attr'] = $global_attr;
+ $module2->info_attr_transform_post['attr'] = $global_attr_post;
+
+ $this->assertEqual($module, $module2);
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleHarness.php
new file mode 100644
index 000000000..7aadf468a
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleHarness.php
@@ -0,0 +1,11 @@
+<?php
+
+class HTMLPurifier_HTMLModuleHarness extends HTMLPurifier_StrategyHarness
+{
+ function setup() {
+ parent::setup();
+ $this->obj = new HTMLPurifier_Strategy_Core();
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleManagerTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleManagerTest.php
new file mode 100644
index 000000000..2750f8924
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleManagerTest.php
@@ -0,0 +1,120 @@
+<?php
+
+class HTMLPurifier_HTMLModuleManagerTest extends HTMLPurifier_Harness
+{
+
+ protected function createManager() {
+ $manager = new HTMLPurifier_HTMLModuleManager();
+
+ $this->config->set('HTML.CustomDoctype', 'Blank');
+ $manager->doctypes->register('Blank');
+
+ $attrdef_nmtokens = new HTMLPurifier_AttrDef_HTML_Nmtokens();
+
+ generate_mock_once('HTMLPurifier_AttrDef');
+ $attrdef = new HTMLPurifier_AttrDefMock();
+ $attrdef->setReturnValue('make', $attrdef_nmtokens);
+ $manager->attrTypes->set('NMTOKENS', $attrdef);
+ return $manager;
+ }
+
+ function test_addModule() {
+
+ $manager = $this->createManager();
+
+ // ...but we add user modules
+
+ $common_module = new HTMLPurifier_HTMLModule();
+ $common_module->name = 'Common';
+ $common_module->attr_collections['Common'] = array('class' => 'NMTOKENS');
+ $common_module->content_sets['Flow'] = 'Block | Inline';
+ $manager->addModule($common_module);
+
+ $structural_module = new HTMLPurifier_HTMLModule();
+ $structural_module->name = 'Structural';
+ $structural_module->addElement('p', 'Block', 'Inline', 'Common');
+ $manager->addModule($structural_module);
+
+ $formatting_module = new HTMLPurifier_HTMLModule();
+ $formatting_module->name = 'Formatting';
+ $formatting_module->addElement('em', 'Inline', 'Inline', 'Common');
+ $manager->addModule($formatting_module);
+
+ $unsafe_module = new HTMLPurifier_HTMLModule();
+ $unsafe_module->name = 'Unsafe';
+ $unsafe_module->safe = false;
+ $unsafe_module->addElement('div', 'Block', 'Flow');
+ $manager->addModule($unsafe_module);
+
+ $config = HTMLPurifier_Config::createDefault();
+ $config->set('HTML.Trusted', false);
+ $config->set('HTML.CustomDoctype', 'Blank');
+
+ $manager->setup($config);
+
+ $attrdef_nmtokens = new HTMLPurifier_AttrDef_HTML_Nmtokens();
+
+ $p = new HTMLPurifier_ElementDef();
+ $p->attr['class'] = $attrdef_nmtokens;
+ $p->child = new HTMLPurifier_ChildDef_Optional(array('em', '#PCDATA'));
+ $p->content_model = 'em | #PCDATA';
+ $p->content_model_type = 'optional';
+ $p->descendants_are_inline = true;
+
+ $em = new HTMLPurifier_ElementDef();
+ $em->attr['class'] = $attrdef_nmtokens;
+ $em->child = new HTMLPurifier_ChildDef_Optional(array('em', '#PCDATA'));
+ $em->content_model = 'em | #PCDATA';
+ $em->content_model_type = 'optional';
+ $em->descendants_are_inline = true;
+
+ $this->assertEqual(
+ array('p' => $p, 'em' => $em),
+ $manager->getElements()
+ );
+
+ // test trusted parameter override
+
+ $div = new HTMLPurifier_ElementDef();
+ $div->child = new HTMLPurifier_ChildDef_Optional(array('p', 'div', 'em', '#PCDATA'));
+ $div->content_model = 'p | div | em | #PCDATA';
+ $div->content_model_type = 'optional';
+ $div->descendants_are_inline = false;
+
+ $this->assertEqual($div, $manager->getElement('div', true));
+
+ }
+
+ function testAllowedModules() {
+
+ $manager = new HTMLPurifier_HTMLModuleManager();
+ $manager->doctypes->register(
+ 'Fantasy Inventory 1.0', true,
+ array('Weapons', 'Magic')
+ );
+
+ // register these modules so it doesn't blow up
+ $weapons_module = new HTMLPurifier_HTMLModule();
+ $weapons_module->name = 'Weapons';
+ $manager->registerModule($weapons_module);
+
+ $magic_module = new HTMLPurifier_HTMLModule();
+ $magic_module->name = 'Magic';
+ $manager->registerModule($magic_module);
+
+ $config = HTMLPurifier_Config::create(array(
+ 'HTML.CustomDoctype' => 'Fantasy Inventory 1.0',
+ 'HTML.AllowedModules' => 'Weapons'
+ ));
+ $manager->setup($config);
+
+ $this->assertTrue( isset($manager->modules['Weapons']));
+ $this->assertFalse(isset($manager->modules['Magic']));
+
+ }
+
+
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleTest.php
new file mode 100644
index 000000000..65220f08b
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleTest.php
@@ -0,0 +1,146 @@
+<?php
+
+class HTMLPurifier_HTMLModuleTest extends HTMLPurifier_Harness
+{
+
+ function test_addElementToContentSet() {
+
+ $module = new HTMLPurifier_HTMLModule();
+
+ $module->addElementToContentSet('b', 'Inline');
+ $this->assertIdentical($module->content_sets, array('Inline' => 'b'));
+
+ $module->addElementToContentSet('i', 'Inline');
+ $this->assertIdentical($module->content_sets, array('Inline' => 'b | i'));
+
+ }
+
+ function test_addElement() {
+
+ $module = new HTMLPurifier_HTMLModule();
+ $def = $module->addElement(
+ 'a', 'Inline', 'Optional: #PCDATA', array('Common'),
+ array(
+ 'href' => 'URI'
+ )
+ );
+
+ $module2 = new HTMLPurifier_HTMLModule();
+ $def2 = new HTMLPurifier_ElementDef();
+ $def2->content_model = '#PCDATA';
+ $def2->content_model_type = 'optional';
+ $def2->attr = array(
+ 'href' => 'URI',
+ 0 => array('Common')
+ );
+ $module2->info['a'] = $def2;
+ $module2->elements = array('a');
+ $module2->content_sets['Inline'] = 'a';
+
+ $this->assertIdentical($module, $module2);
+ $this->assertIdentical($def, $def2);
+ $this->assertReference($def, $module->info['a']);
+
+ }
+
+ function test_parseContents() {
+
+ $module = new HTMLPurifier_HTMLModule();
+
+ // pre-defined templates
+ $this->assertIdentical(
+ $module->parseContents('Inline'),
+ array('optional', 'Inline | #PCDATA')
+ );
+ $this->assertIdentical(
+ $module->parseContents('Flow'),
+ array('optional', 'Flow | #PCDATA')
+ );
+ $this->assertIdentical(
+ $module->parseContents('Empty'),
+ array('empty', '')
+ );
+
+ // normalization procedures
+ $this->assertIdentical(
+ $module->parseContents('optional: a'),
+ array('optional', 'a')
+ );
+ $this->assertIdentical(
+ $module->parseContents('OPTIONAL :a'),
+ array('optional', 'a')
+ );
+ $this->assertIdentical(
+ $module->parseContents('Optional: a'),
+ array('optional', 'a')
+ );
+
+ // others
+ $this->assertIdentical(
+ $module->parseContents('Optional: a | b | c'),
+ array('optional', 'a | b | c')
+ );
+
+ // object pass-through
+ generate_mock_once('HTMLPurifier_AttrDef');
+ $this->assertIdentical(
+ $module->parseContents(new HTMLPurifier_AttrDefMock()),
+ array(null, null)
+ );
+
+ }
+
+ function test_mergeInAttrIncludes() {
+
+ $module = new HTMLPurifier_HTMLModule();
+
+ $attr = array();
+ $module->mergeInAttrIncludes($attr, 'Common');
+ $this->assertIdentical($attr, array(0 => array('Common')));
+
+ $attr = array('a' => 'b');
+ $module->mergeInAttrIncludes($attr, array('Common', 'Good'));
+ $this->assertIdentical($attr, array('a' => 'b', 0 => array('Common', 'Good')));
+
+ }
+
+ function test_addBlankElement() {
+
+ $module = new HTMLPurifier_HTMLModule();
+ $def = $module->addBlankElement('a');
+
+ $def2 = new HTMLPurifier_ElementDef();
+ $def2->standalone = false;
+
+ $this->assertReference($module->info['a'], $def);
+ $this->assertIdentical($def, $def2);
+
+ }
+
+ function test_makeLookup() {
+
+ $module = new HTMLPurifier_HTMLModule();
+
+ $this->assertIdentical(
+ $module->makeLookup('foo'),
+ array('foo' => true)
+ );
+ $this->assertIdentical(
+ $module->makeLookup(array('foo')),
+ array('foo' => true)
+ );
+
+ $this->assertIdentical(
+ $module->makeLookup('foo', 'two'),
+ array('foo' => true, 'two' => true)
+ );
+ $this->assertIdentical(
+ $module->makeLookup(array('foo', 'two')),
+ array('foo' => true, 'two' => true)
+ );
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT.php
new file mode 100644
index 000000000..06e7958fb
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT.php
@@ -0,0 +1,33 @@
+<?php
+
+class HTMLPurifier_HTMLT extends HTMLPurifier_Harness
+{
+ protected $path;
+
+ public function __construct($path) {
+ $this->path = $path;
+ parent::__construct($path);
+ }
+
+ public function testHtmlt() {
+ $parser = new HTMLPurifier_StringHashParser();
+ $hash = $parser->parseFile($this->path); // assume parser normalizes to "\n"
+ if (isset($hash['SKIPIF'])) {
+ if (eval($hash['SKIPIF'])) return;
+ }
+ $this->config->set('Output.Newline', "\n");
+ if (isset($hash['INI'])) {
+ // there should be a more efficient way than writing another
+ // ini file every time... probably means building a parser for
+ // ini (check out the yaml implementation we saw somewhere else)
+ $ini_file = $this->path . '.ini';
+ file_put_contents($ini_file, $hash['INI']);
+ $this->config->loadIni($ini_file);
+ }
+ $expect = isset($hash['EXPECT']) ? $hash['EXPECT'] : $hash['HTML'];
+ $this->assertPurification(rtrim($hash['HTML']), rtrim($expect));
+ if (isset($hash['INI'])) unlink($ini_file);
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/allowed-preserve.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/allowed-preserve.htmlt
new file mode 100644
index 000000000..650f00b6b
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/allowed-preserve.htmlt
@@ -0,0 +1,8 @@
+--INI--
+HTML.AllowedElements = b,i,p,a
+HTML.AllowedAttributes = a.href,*.id
+--HTML--
+<p>Par.</p>
+<p>Para<a href="http://google.com/">gr</a>aph</p>
+Text<b>Bol<i>d</i></b>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/allowed-remove.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/allowed-remove.htmlt
new file mode 100644
index 000000000..2b6b8eea6
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/allowed-remove.htmlt
@@ -0,0 +1,8 @@
+--INI--
+HTML.AllowedElements = b,i,p,a
+HTML.AllowedAttributes = a.href,*.id
+--HTML--
+<span>Not allowed</span><a class="mef" id="foobar">Remove id too!</a>
+--EXPECT--
+Not allowed<a>Remove id too!</a>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/basic.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/basic.htmlt
new file mode 100644
index 000000000..4c1f42778
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/basic.htmlt
@@ -0,0 +1,5 @@
+--HTML--
+<b>basic</b>
+--EXPECT--
+<b>basic</b>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/blacklist-preserve.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/blacklist-preserve.htmlt
new file mode 100644
index 000000000..d8882fe22
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/blacklist-preserve.htmlt
@@ -0,0 +1,6 @@
+--INI--
+HTML.ForbiddenElements = b
+HTML.ForbiddenAttributes = a@href
+--HTML--
+<p>foo</p>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/blacklist-remove.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/blacklist-remove.htmlt
new file mode 100644
index 000000000..1b5985873
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/blacklist-remove.htmlt
@@ -0,0 +1,8 @@
+--INI--
+HTML.ForbiddenElements = b
+HTML.ForbiddenAttributes = a@href
+--HTML--
+<b>Foo<a href="bar">bar</a></b>
+--EXPECT--
+Foo<a>bar</a>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/css-allowed-preserve.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/css-allowed-preserve.htmlt
new file mode 100644
index 000000000..a5fe3f849
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/css-allowed-preserve.htmlt
@@ -0,0 +1,5 @@
+--INI--
+CSS.AllowedProperties = color,background-color
+--HTML--
+<div style="color:#f00;background-color:#ded;">red</div>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/css-allowed-remove.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/css-allowed-remove.htmlt
new file mode 100644
index 000000000..644b7001a
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/css-allowed-remove.htmlt
@@ -0,0 +1,7 @@
+--INI--
+CSS.AllowedProperties = color,background-color
+--HTML--
+<div style="color:#f00;border:1px solid #000">red</div>
+--EXPECT--
+<div style="color:#f00;">red</div>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/disable-uri.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/disable-uri.htmlt
new file mode 100644
index 000000000..fa692e444
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/disable-uri.htmlt
@@ -0,0 +1,6 @@
+--INI--
+URI.Disable = true
+--HTML--
+<img src="foobar" />
+--EXPECT--
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/double-youtube.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/double-youtube.htmlt
new file mode 100644
index 000000000..a46ce4736
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/double-youtube.htmlt
@@ -0,0 +1,6 @@
+--INI--
+HTML.SafeObject = true
+Output.FlashCompat = true
+--HTML--
+<object width="425" height="350" data="http://www.youtube.com/v/BdU--T8rLns" type="application/x-shockwave-flash"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><param name="movie" value="http://www.youtube.com/v/BdU--T8rLns" /><param name="wmode" value="window" /></object>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/empty.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/empty.htmlt
new file mode 100644
index 000000000..9094c47b0
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/empty.htmlt
@@ -0,0 +1,6 @@
+--INI--
+
+--HTML--
+
+--EXPECT--
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/file-uri.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/file-uri.htmlt
new file mode 100644
index 000000000..5b9e34a8c
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/file-uri.htmlt
@@ -0,0 +1,5 @@
+--INI--
+URI.AllowedSchemes = file
+--HTML--
+<a href="file:///foo">foo</a>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-default.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-default.htmlt
new file mode 100644
index 000000000..1c1460f2c
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-default.htmlt
@@ -0,0 +1,5 @@
+--HTML--
+<span id="moon">foobar</span>
+--EXPECT--
+<span>foobar</span>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-enabled.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-enabled.htmlt
new file mode 100644
index 000000000..70ddcf99f
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-enabled.htmlt
@@ -0,0 +1,6 @@
+--INI--
+Attr.EnableID = true
+--HTML--
+<span id="moon">foobar</span>
+<img id="folly" src="folly.png" alt="Omigosh!" />
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-img.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-img.htmlt
new file mode 100644
index 000000000..12755bea3
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-img.htmlt
@@ -0,0 +1,8 @@
+--INI--
+Attr.EnableID = true
+Core.LexerImpl = DirectLex
+--HTML--
+<img src="img_11775.jpg" alt="[Img #11775]" id="EMBEDDED_IMG_11775" >
+--EXPECT--
+<img src="img_11775.jpg" alt="[Img #11775]" id="EMBEDDED_IMG_11775" />
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-name-mix.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-name-mix.htmlt
new file mode 100644
index 000000000..a48da8e62
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-name-mix.htmlt
@@ -0,0 +1,11 @@
+--INI--
+Attr.EnableID = true
+--HTML--
+<a name="foo" id="foo">Test</a>
+<a name="foo">Test2</a>
+<a name="bar" id="baz">Test3</a>
+--EXPECT--
+<a name="foo" id="foo">Test</a>
+<a>Test2</a>
+<a name="bar" id="baz">Test3</a>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/inline-list-loop.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/inline-list-loop.htmlt
new file mode 100644
index 000000000..0a63e8955
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/inline-list-loop.htmlt
@@ -0,0 +1,5 @@
+--HTML--
+<i><ul></ul></i>
+--EXPECT--
+<i></i><i></i>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/inline-wraps-block.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/inline-wraps-block.htmlt
new file mode 100644
index 000000000..da6bae689
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/inline-wraps-block.htmlt
@@ -0,0 +1,5 @@
+--HTML--
+<a href="foo.html"><h1>Foobar</h1></a>
+--EXPECT--
+<a href="foo.html"></a><h1><a href="foo.html">Foobar</a></h1><a href="foo.html"></a>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/list-nesting.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/list-nesting.htmlt
new file mode 100644
index 000000000..22ebf6058
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/list-nesting.htmlt
@@ -0,0 +1,5 @@
+--HTML--
+<ul><li>Sublist 1</li><ul><li>Bullet</li></ul></ul>
+--EXPECT--
+<ul><li>Sublist 1<ul><li>Bullet</li></ul></li></ul>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/munge-extra.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/munge-extra.htmlt
new file mode 100644
index 000000000..4b1c70a9d
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/munge-extra.htmlt
@@ -0,0 +1,11 @@
+--INI--
+URI.Munge = "/redirect?s=%s&t=%t&r=%r&n=%n&m=%m&p=%p"
+URI.MungeSecretKey = "foo"
+URI.MungeResources = true
+--HTML--
+<a href="http://example.com">Link</a>
+<img src="http://example.com" style="background-image:url(http://example.com);" alt="example.com" />
+--EXPECT--
+<a href="/redirect?s=http%3A%2F%2Fexample.com&amp;t=c15354f3953dfec262c55b1403067e0d045a3059&amp;r=&amp;n=a&amp;m=href&amp;p=">Link</a>
+<img src="/redirect?s=http%3A%2F%2Fexample.com&amp;t=c15354f3953dfec262c55b1403067e0d045a3059&amp;r=1&amp;n=img&amp;m=src&amp;p=" style="background-image:url(&quot;/redirect?s=http%3A%2F%2Fexample.com&amp;t=c15354f3953dfec262c55b1403067e0d045a3059&amp;r=1&amp;n=img&amp;m=style&amp;p=background-image&quot;);" alt="example.com" />
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/munge.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/munge.htmlt
new file mode 100644
index 000000000..827c216e6
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/munge.htmlt
@@ -0,0 +1,52 @@
+--INI--
+URI.Munge = "/r/%s"
+URI.AllowedSchemes = http,ftp,file
+--HTML--
+<a href="google.com">foo</a>
+<a href="/google.com">foo</a>
+<a href="//google.com">foo</a>
+<a href="///google.com">foo</a>
+<a href="////google.com">foo</a>
+
+<a href="http:google.com">foo</a>
+<a href="http:/google.com">foo</a>
+<a href="http://google.com">foo</a>
+<a href="http:///google.com">foo</a>
+<a href="http:////google.com">foo</a>
+
+<a href="ftp:google.com">foo</a>
+<a href="ftp:/google.com">foo</a>
+<a href="ftp://google.com">foo</a>
+<a href="ftp:///google.com">foo</a>
+<a href="ftp:////google.com">foo</a>
+
+<a href="file:google.com">foo</a>
+<a href="file:/google.com">foo</a>
+<a href="file://google.com">foo</a>
+<a href="file:///google.com">foo</a>
+<a href="file:////google.com">foo</a>
+--EXPECT--
+<a href="google.com">foo</a>
+<a href="/google.com">foo</a>
+<a href="/r/%2F%2Fgoogle.com">foo</a>
+<a href="/google.com">foo</a>
+<a>foo</a>
+
+<a href="google.com">foo</a>
+<a href="/google.com">foo</a>
+<a href="/r/http%3A%2F%2Fgoogle.com">foo</a>
+<a href="/google.com">foo</a>
+<a>foo</a>
+
+<a>foo</a>
+<a>foo</a>
+<a href="/r/ftp%3A%2F%2Fgoogle.com">foo</a>
+<a>foo</a>
+<a>foo</a>
+
+<a href="file:google.com">foo</a>
+<a href="file:/google.com">foo</a>
+<a href="file://google.com">foo</a>
+<a href="file:///google.com">foo</a>
+<a href="file:////google.com">foo</a>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/name.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/name.htmlt
new file mode 100644
index 000000000..1713ed417
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/name.htmlt
@@ -0,0 +1,6 @@
+--INI--
+Attr.EnableID = true
+HTML.Doctype = "XHTML 1.0 Strict"
+--HTML--
+<a name="asdf"></a>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-googlemaps.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-googlemaps.htmlt
new file mode 100644
index 000000000..40fac62d5
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-googlemaps.htmlt
@@ -0,0 +1,8 @@
+--INI--
+HTML.SafeIframe = true
+URI.SafeIframeRegexp = "%^http://maps.google.com/%"
+--HTML--
+<iframe width="425" height="350" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="http://maps.google.com/?ie=UTF8&amp;ll=37.0625,-95.677068&amp;spn=24.455808,37.353516&amp;z=4&amp;output=embed"></iframe>
+--EXPECT--
+<iframe width="425" height="350" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="http://maps.google.com/?ie=UTF8&amp;ll=37.0625,-95.677068&amp;spn=24.455808,37.353516&amp;z=4&amp;output=embed"></iframe>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-invalid.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-invalid.htmlt
new file mode 100644
index 000000000..5b366da54
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-invalid.htmlt
@@ -0,0 +1,7 @@
+--INI--
+HTML.SafeIframe = true
+--HTML--
+<iframe src="http://www.example.com/"></iframe>
+--EXPECT--
+<iframe></iframe>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-youtube.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-youtube.htmlt
new file mode 100644
index 000000000..1abc2c827
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-youtube.htmlt
@@ -0,0 +1,8 @@
+--INI--
+HTML.SafeIframe = true
+URI.SafeIframeRegexp = "%^http://www.youtube.com/embed/%"
+--HTML--
+<iframe title="YouTube video player" width="480" height="390" src="http://www.youtube.com/embed/RVtEQxH7PWA" frameborder="0" allowfullscreen></iframe>
+--EXPECT--
+<iframe title="YouTube video player" width="480" height="390" src="http://www.youtube.com/embed/RVtEQxH7PWA" frameborder="0"></iframe>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe.htmlt
new file mode 100644
index 000000000..7c0b60d2f
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe.htmlt
@@ -0,0 +1,14 @@
+--INI--
+HTML.SafeIframe = true
+URI.SafeIframeRegexp = "%(^http://www.example.com/|^https?://dev.example.com/)%"
+--HTML--
+<iframe src="http://www.example.com/"></iframe>
+<iframe src="http://malicious.host.com/?http://www.example.com/"></iframe>
+<iframe src="http://dev.example.com/"></iframe>
+<iframe src="https://dev.example.com/"></iframe>
+--EXPECT--
+<iframe src="http://www.example.com/"></iframe>
+<iframe></iframe>
+<iframe src="http://dev.example.com/"></iframe>
+<iframe src="https://dev.example.com/"></iframe>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-object-embed-munge.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-object-embed-munge.htmlt
new file mode 100644
index 000000000..77b32d3a1
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-object-embed-munge.htmlt
@@ -0,0 +1,10 @@
+--INI--
+HTML.SafeObject = true
+HTML.SafeEmbed = true
+URI.Munge = "/redirect.php?url=%s&check=%t"
+URI.MungeSecretKey = "foo"
+--HTML--
+<object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/Oq3FV_zdyy0&hl=en"></param><embed src="http://www.youtube.com/v/Oq3FV_zdyy0&hl=en" type="application/x-shockwave-flash" width="425" height="344"></embed></object>
+--EXPECT--
+<object width="425" height="344" data="http://www.youtube.com/v/Oq3FV_zdyy0&amp;hl=en" type="application/x-shockwave-flash"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><param name="movie" value="http://www.youtube.com/v/Oq3FV_zdyy0&amp;hl=en" /><embed src="http://www.youtube.com/v/Oq3FV_zdyy0&amp;hl=en" type="application/x-shockwave-flash" width="425" height="344" allowscriptaccess="never" allownetworking="internal" /></object>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-object-embed.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-object-embed.htmlt
new file mode 100644
index 000000000..f4bb0ff09
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-object-embed.htmlt
@@ -0,0 +1,8 @@
+--INI--
+HTML.SafeObject = true
+HTML.SafeEmbed = true
+--HTML--
+<object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/Oq3FV_zdyy0&hl=en"></param><embed src="http://www.youtube.com/v/Oq3FV_zdyy0&hl=en" type="application/x-shockwave-flash" width="425" height="344"></embed></object>
+--EXPECT--
+<object width="425" height="344" data="http://www.youtube.com/v/Oq3FV_zdyy0&amp;hl=en" type="application/x-shockwave-flash"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><param name="movie" value="http://www.youtube.com/v/Oq3FV_zdyy0&amp;hl=en" /><embed src="http://www.youtube.com/v/Oq3FV_zdyy0&amp;hl=en" type="application/x-shockwave-flash" width="425" height="344" allowscriptaccess="never" allownetworking="internal" /></object>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-bare.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-bare.htmlt
new file mode 100644
index 000000000..f85c2fb5e
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-bare.htmlt
@@ -0,0 +1,9 @@
+--INI--
+HTML.Trusted = true
+--HTML--
+<script type="text/javascript">alert("<This is compatible with XHTML>");</script>
+--EXPECT--
+<script type="text/javascript"><!--//--><![CDATA[//><!--
+alert("<This is compatible with XHTML>");
+//--><!]]></script>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-cdata.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-cdata.htmlt
new file mode 100644
index 000000000..e7d6d7f5b
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-cdata.htmlt
@@ -0,0 +1,11 @@
+--INI--
+HTML.Trusted = true
+--HTML--
+<script type="text/javascript"><![CDATA[
+alert("<This is compatible with XHTML>");
+]]></script>
+--EXPECT--
+<script type="text/javascript"><!--//--><![CDATA[//><!--
+alert("<This is compatible with XHTML>");
+//--><!]]></script>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-comment.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-comment.htmlt
new file mode 100644
index 000000000..006901919
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-comment.htmlt
@@ -0,0 +1,11 @@
+--INI--
+HTML.Trusted = true
+--HTML--
+<script type="text/javascript"><!--
+alert("<This is compatible with XHTML>");
+//--></script>
+--EXPECT--
+<script type="text/javascript"><!--//--><![CDATA[//><!--
+alert("<This is compatible with XHTML>");
+//--><!]]></script>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-dbl-comment.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-dbl-comment.htmlt
new file mode 100644
index 000000000..ef899b597
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-dbl-comment.htmlt
@@ -0,0 +1,11 @@
+--INI--
+HTML.Trusted = true
+--HTML--
+<script type="text/javascript"><![CDATA[
+alert("<This is compatible with XHTML>");
+//]]></script>
+--EXPECT--
+<script type="text/javascript"><!--//--><![CDATA[//><!--
+alert("<This is compatible with XHTML>");
+//--><!]]></script>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-ideal.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-ideal.htmlt
new file mode 100644
index 000000000..70510687c
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-ideal.htmlt
@@ -0,0 +1,11 @@
+--INI--
+HTML.Trusted = true
+--HTML--
+<script type="text/javascript"><!--//--><![CDATA[//><!--
+alert("<This is compatible with XHTML>");
+//--><!]]></script>
+--EXPECT--
+<script type="text/javascript"><!--//--><![CDATA[//><!--
+alert("<This is compatible with XHTML>");
+//--><!]]></script>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/secure-munge.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/secure-munge.htmlt
new file mode 100644
index 000000000..114cb0000
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/secure-munge.htmlt
@@ -0,0 +1,10 @@
+--INI--
+URI.Munge = "/redirect.php?url=%s&check=%t"
+URI.MungeSecretKey = "foo"
+--HTML--
+<a href="http://localhost">foo</a>
+<img src="http://localhost" alt="local" />
+--EXPECT--
+<a href="/redirect.php?url=http%3A%2F%2Flocalhost&amp;check=8e8223ae8fac24561104180ea549c21fbd111be7">foo</a>
+<img src="http://localhost" alt="local" />
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/shift-jis-preserve-yen.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/shift-jis-preserve-yen.htmlt
new file mode 100644
index 000000000..f22417c01
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/shift-jis-preserve-yen.htmlt
@@ -0,0 +1,8 @@
+--SKIPIF--
+if (!function_exists('iconv')) return true;
+--INI--
+Core.Encoding = "Shift_JIS"
+Core.EscapeNonASCIICharacters = true
+--HTML--
+<b style="font-family:'&#165;';">111</b>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/shift-jis-remove-yen.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/shift-jis-remove-yen.htmlt
new file mode 100644
index 000000000..6c2d3bc4c
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/shift-jis-remove-yen.htmlt
@@ -0,0 +1,9 @@
+--SKIPIF--
+if (!function_exists('iconv')) return true;
+--INI--
+Core.Encoding = Shift_JIS
+--HTML--
+<b style="font-family:'&#165;';">111</b>
+--EXPECT--
+<b style="font-family:'';">111</b>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-blockquote-with-inline.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-blockquote-with-inline.htmlt
new file mode 100644
index 000000000..a6eb0ec66
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-blockquote-with-inline.htmlt
@@ -0,0 +1,7 @@
+--INI--
+HTML.Doctype = "XHTML 1.0 Strict"
+--HTML--
+<blockquote>Illegal <b>contents</b></blockquote>
+--EXPECT--
+<blockquote><p>Illegal <b>contents</b></p></blockquote>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-blockquote.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-blockquote.htmlt
new file mode 100644
index 000000000..b61e8dc07
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-blockquote.htmlt
@@ -0,0 +1,7 @@
+--INI--
+HTML.Strict = true
+--HTML--
+<blockquote>Illegal contents</blockquote>
+--EXPECT--
+<blockquote><p>Illegal contents</p></blockquote>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-underline.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-underline.htmlt
new file mode 100644
index 000000000..a2e18b1c9
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-underline.htmlt
@@ -0,0 +1,7 @@
+--INI--
+HTML.Strict = true
+--HTML--
+<u>Illegal underline</u>
+--EXPECT--
+<span style="text-decoration:underline;">Illegal underline</span>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/tidy-background.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/tidy-background.htmlt
new file mode 100644
index 000000000..08bda2678
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/tidy-background.htmlt
@@ -0,0 +1,5 @@
+--HTML--
+<table background="logo.png"><tr><td>asdf</td></tr></table>
+--EXPECT--
+<table style="background-image:url(&quot;logo.png&quot;);"><tr><td>asdf</td></tr></table>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments-required.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments-required.htmlt
new file mode 100644
index 000000000..62dc159d0
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments-required.htmlt
@@ -0,0 +1,6 @@
+--INI--
+HTML.Trusted = true
+--HTML--
+<ul><!-- Foo --></ul>
+--EXPECT--
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments-table.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments-table.htmlt
new file mode 100644
index 000000000..89d80febf
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments-table.htmlt
@@ -0,0 +1,5 @@
+--INI--
+HTML.Trusted = true
+--HTML--
+<table><!-- foo --><tr><td>Foo</td></tr></table>
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments.htmlt
new file mode 100644
index 000000000..93b4a35a6
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments.htmlt
@@ -0,0 +1,5 @@
+--INI--
+HTML.Trusted = true
+--HTML--
+<!-- Foobar -->
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/whitespace-preserve.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/whitespace-preserve.htmlt
new file mode 100644
index 000000000..861bddaf4
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/whitespace-preserve.htmlt
@@ -0,0 +1,3 @@
+--HTML--
+Foo<b> </b>bar
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Harness.php b/lib/htmlpurifier/tests/HTMLPurifier/Harness.php
new file mode 100644
index 000000000..d6490a117
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Harness.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * All-use harness, use this rather than SimpleTest's
+ */
+class HTMLPurifier_Harness extends UnitTestCase
+{
+
+ public function __construct($name = null) {
+ parent::__construct($name);
+ }
+
+ protected $config, $context, $purifier;
+
+ /**
+ * Generates easily accessible default config/context, as well as
+ * a convenience purifier for integration testing.
+ */
+ public function setUp() {
+ list($this->config, $this->context) = $this->createCommon();
+ $this->config->set('Output.Newline', '
+');
+ $this->purifier = new HTMLPurifier();
+ }
+
+ /**
+ * Asserts a purification. Good for integration testing.
+ */
+ function assertPurification($input, $expect = null) {
+ if ($expect === null) $expect = $input;
+ $result = $this->purifier->purify($input, $this->config);
+ $this->assertIdentical($expect, $result);
+ }
+
+
+ /**
+ * Accepts config and context and prepares them into a valid state
+ * @param &$config Reference to config variable
+ * @param &$context Reference to context variable
+ */
+ protected function prepareCommon(&$config, &$context) {
+ $config = HTMLPurifier_Config::create($config);
+ if (!$context) $context = new HTMLPurifier_Context();
+ }
+
+ /**
+ * Generates default configuration and context objects
+ * @return Defaults in form of array($config, $context)
+ */
+ protected function createCommon() {
+ return array(HTMLPurifier_Config::createDefault(), new HTMLPurifier_Context);
+ }
+
+ /**
+ * Normalizes a string to Unix (\n) endings
+ */
+ protected function normalize(&$string) {
+ $string = str_replace(array("\r\n", "\r"), "\n", $string);
+ }
+
+ /**
+ * If $expect is false, ignore $result and check if status failed.
+ * Otherwise, check if $status if true and $result === $expect.
+ * @param $status Boolean status
+ * @param $result Mixed result from processing
+ * @param $expect Mixed expectation for result
+ */
+ protected function assertEitherFailOrIdentical($status, $result, $expect) {
+ if ($expect === false) {
+ $this->assertFalse($status, 'Expected false result, got true');
+ } else {
+ $this->assertTrue($status, 'Expected true result, got false');
+ $this->assertIdentical($result, $expect);
+ }
+ }
+
+ public function getTests() {
+ // __onlytest makes only one test get triggered
+ foreach (get_class_methods(get_class($this)) as $method) {
+ if (strtolower(substr($method, 0, 10)) == '__onlytest') {
+ $this->reporter->paintSkip('All test methods besides ' . $method);
+ return array($method);
+ }
+ }
+ return parent::getTests();
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/IDAccumulatorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/IDAccumulatorTest.php
new file mode 100644
index 000000000..90bca0739
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/IDAccumulatorTest.php
@@ -0,0 +1,39 @@
+<?php
+
+class HTMLPurifier_IDAccumulatorTest extends HTMLPurifier_Harness
+{
+
+ function test() {
+
+ // initialize the accumulator
+ $accumulator = new HTMLPurifier_IDAccumulator();
+
+ $this->assertTrue( $accumulator->add('id1'));
+ $this->assertTrue( $accumulator->add('id2'));
+ $this->assertFalse($accumulator->add('id1')); // repeated id
+
+ // you can also access the properties (they're public)
+ $this->assertTrue( isset($accumulator->ids['id2']) );
+
+ }
+
+ function testLoad() {
+
+ $accumulator = new HTMLPurifier_IDAccumulator();
+
+ $accumulator->load(array('id1', 'id2', 'id3'));
+
+ $this->assertFalse($accumulator->add('id1')); // repeated id
+ $this->assertTrue($accumulator->add('id4'));
+
+ }
+
+ function testBuild() {
+ $this->config->set('Attr.IDBlacklist', array('foo'));
+ $accumulator = HTMLPurifier_IDAccumulator::build($this->config, $this->context);
+ $this->assertTrue( isset($accumulator->ids['foo']) );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Injector/AutoParagraphTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Injector/AutoParagraphTest.php
new file mode 100644
index 000000000..e6abbd1ff
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Injector/AutoParagraphTest.php
@@ -0,0 +1,515 @@
+<?php
+
+class HTMLPurifier_Injector_AutoParagraphTest extends HTMLPurifier_InjectorHarness
+{
+
+ function setup() {
+ parent::setup();
+ $this->config->set('AutoFormat.AutoParagraph', true);
+ }
+
+ function testSingleParagraph() {
+ $this->assertResult(
+ 'Foobar',
+ '<p>Foobar</p>'
+ );
+ }
+
+ function testSingleMultiLineParagraph() {
+ $this->assertResult(
+'Par 1
+Par 1 still',
+'<p>Par 1
+Par 1 still</p>'
+ );
+ }
+
+ function testTwoParagraphs() {
+ $this->assertResult(
+'Par1
+
+Par2',
+"<p>Par1</p>
+
+<p>Par2</p>"
+ );
+ }
+
+ function testTwoParagraphsWithLotsOfSpace() {
+ $this->assertResult(
+'Par1
+
+
+
+Par2',
+'<p>Par1</p>
+
+<p>Par2</p>'
+ );
+ }
+
+ function testTwoParagraphsWithInlineElements() {
+ $this->assertResult(
+'<b>Par1</b>
+
+<i>Par2</i>',
+'<p><b>Par1</b></p>
+
+<p><i>Par2</i></p>'
+ );
+ }
+
+ function testSingleParagraphThatLooksLikeTwo() {
+ $this->assertResult(
+'<b>Par1
+
+Par2</b>',
+'<p><b>Par1
+
+Par2</b></p>'
+ );
+ }
+
+ function testAddParagraphAdjacentToParagraph() {
+ $this->assertResult(
+ 'Par1<p>Par2</p>',
+'<p>Par1</p>
+
+<p>Par2</p>'
+ );
+ }
+
+ function testParagraphUnclosedInlineElement() {
+ $this->assertResult(
+ '<b>Par1',
+ '<p><b>Par1</b></p>'
+ );
+ }
+
+ function testPreservePreTags() {
+ $this->assertResult(
+'<pre>Par1
+
+Par1</pre>'
+ );
+ }
+
+ function testIgnoreTrailingWhitespace() {
+ $this->assertResult(
+'Par1
+
+ ',
+'<p>Par1</p>
+
+'
+ );
+ }
+
+ function testDoNotParagraphBlockElements() {
+ $this->assertResult(
+'Par1
+
+<div>Par2</div>
+
+Par3',
+'<p>Par1</p>
+
+<div>Par2</div>
+
+<p>Par3</p>'
+ );
+ }
+
+ function testParagraphTextAndInlineNodes() {
+ $this->assertResult(
+'Par<b>1</b>',
+ '<p>Par<b>1</b></p>'
+ );
+ }
+
+ function testPreserveLeadingWhitespace() {
+ $this->assertResult(
+'
+
+Par',
+'
+
+<p>Par</p>'
+ );
+ }
+
+ function testPreserveSurroundingWhitespace() {
+ $this->assertResult(
+'
+
+Par
+
+',
+'
+
+<p>Par</p>
+
+'
+ );
+ }
+
+ function testParagraphInsideBlockNode() {
+ $this->assertResult(
+'<div>Par1
+
+Par2</div>',
+'<div><p>Par1</p>
+
+<p>Par2</p></div>'
+ );
+ }
+
+ function testParagraphInlineNodeInsideBlockNode() {
+ $this->assertResult(
+'<div><b>Par1</b>
+
+Par2</div>',
+'<div><p><b>Par1</b></p>
+
+<p>Par2</p></div>'
+ );
+ }
+
+ function testNoParagraphWhenOnlyOneInsideBlockNode() {
+ $this->assertResult('<div>Par1</div>');
+ }
+
+ function testParagraphTwoInlineNodesInsideBlockNode() {
+ $this->assertResult(
+'<div><b>Par1</b>
+
+<i>Par2</i></div>',
+'<div><p><b>Par1</b></p>
+
+<p><i>Par2</i></p></div>'
+ );
+ }
+
+ function testPreserveInlineNodesInPreTag() {
+ $this->assertResult(
+'<pre><b>Par1</b>
+
+<i>Par2</i></pre>'
+ );
+ }
+
+ function testSplitUpInternalsOfPTagInBlockNode() {
+ $this->assertResult(
+'<div><p>Foo
+
+Bar</p></div>',
+'<div><p>Foo</p>
+
+<p>Bar</p></div>'
+ );
+ }
+
+ function testSplitUpInlineNodesInPTagInBlockNode() {
+ $this->assertResult(
+'<div><p><b>Foo</b>
+
+<i>Bar</i></p></div>',
+'<div><p><b>Foo</b></p>
+
+<p><i>Bar</i></p></div>'
+ );
+ }
+
+ function testNoParagraphSingleInlineNodeInBlockNode() {
+ $this->assertResult( '<div><b>Foo</b></div>' );
+ }
+
+ function testParagraphInBlockquote() {
+ $this->assertResult(
+'<blockquote>Par1
+
+Par2</blockquote>',
+'<blockquote><p>Par1</p>
+
+<p>Par2</p></blockquote>'
+ );
+ }
+
+ function testNoParagraphBetweenListItem() {
+ $this->assertResult(
+'<ul><li>Foo</li>
+
+<li>Bar</li></ul>'
+ );
+ }
+
+ function testParagraphSingleElementWithSurroundingSpace() {
+ $this->assertResult(
+'<div>
+
+Bar
+
+</div>',
+ '<div>
+
+<p>Bar</p>
+
+</div>'
+ );
+ }
+
+ function testIgnoreExtraSpaceWithLeadingInlineNode() {
+ $this->assertResult(
+'<b>Par1</b>a
+
+
+
+Par2',
+'<p><b>Par1</b>a</p>
+
+<p>Par2</p>'
+ );
+ }
+
+ function testAbsorbExtraEndingPTag() {
+ $this->assertResult(
+'Par1
+
+Par2</p>',
+'<p>Par1</p>
+
+<p>Par2</p>'
+ );
+ }
+
+ function testAbsorbExtraEndingDivTag() {
+ $this->assertResult(
+'Par1
+
+Par2</div>',
+'<p>Par1</p>
+
+<p>Par2</p>'
+ );
+ }
+
+ function testDoNotParagraphSingleSurroundingSpaceInBlockNode() {
+ $this->assertResult(
+'<div>
+Par1
+</div>'
+ );
+ }
+
+ function testBlockNodeTextDelimeterInBlockNode() {
+ $this->assertResult(
+'<div>Par1
+
+<div>Par2</div></div>',
+'<div><p>Par1</p>
+
+<div>Par2</div></div>'
+ );
+ }
+
+ function testBlockNodeTextDelimeterWithoutDoublespaceInBlockNode() {
+ $this->assertResult(
+'<div>Par1
+<div>Par2</div></div>'
+ );
+ }
+
+ function testBlockNodeTextDelimeterWithoutDoublespace() {
+ $this->assertResult(
+'Par1
+<div>Par2</div>',
+'<p>Par1
+</p>
+
+<div>Par2</div>'
+ );
+ }
+
+ function testTwoParagraphsOfTextAndInlineNode() {
+ $this->assertResult(
+'Par1
+
+<b>Par2</b>',
+'<p>Par1</p>
+
+<p><b>Par2</b></p>'
+ );
+ }
+
+ function testLeadingInlineNodeParagraph() {
+ $this->assertResult(
+'<img /> Foo',
+'<p><img /> Foo</p>'
+ );
+ }
+
+ function testTrailingInlineNodeParagraph() {
+ $this->assertResult(
+'<li>Foo <a>bar</a></li>'
+ );
+ }
+
+ function testTwoInlineNodeParagraph() {
+ $this->assertResult(
+'<li><b>baz</b><a>bar</a></li>'
+ );
+ }
+
+ function testNoParagraphTrailingBlockNodeInBlockNode() {
+ $this->assertResult(
+'<div><div>asdf</div><b>asdf</b></div>'
+ );
+ }
+
+ function testParagraphTrailingBlockNodeWithDoublespaceInBlockNode() {
+ $this->assertResult(
+'<div><div>asdf</div>
+
+<b>asdf</b></div>',
+'<div><div>asdf</div>
+
+<p><b>asdf</b></p></div>'
+ );
+ }
+
+ function testParagraphTwoInlineNodesAndWhitespaceNode() {
+ $this->assertResult(
+'<b>One</b> <i>Two</i>',
+'<p><b>One</b> <i>Two</i></p>'
+ );
+ }
+
+ function testNoParagraphWithInlineRootNode() {
+ $this->config->set('HTML.Parent', 'span');
+ $this->assertResult(
+'Par
+
+Par2'
+ );
+ }
+
+ function testInlineAndBlockTagInDivNoParagraph() {
+ $this->assertResult(
+ '<div><code>bar</code> mmm <pre>asdf</pre></div>'
+ );
+ }
+
+ function testInlineAndBlockTagInDivNeedingParagraph() {
+ $this->assertResult(
+'<div><code>bar</code> mmm
+
+<pre>asdf</pre></div>',
+'<div><p><code>bar</code> mmm</p>
+
+<pre>asdf</pre></div>'
+ );
+ }
+
+ function testTextInlineNodeTextThenDoubleNewlineNeedsParagraph() {
+ $this->assertResult(
+'<div>asdf <code>bar</code> mmm
+
+<pre>asdf</pre></div>',
+'<div><p>asdf <code>bar</code> mmm</p>
+
+<pre>asdf</pre></div>'
+ );
+ }
+
+ function testUpcomingTokenHasNewline() {
+ $this->assertResult(
+'<div>Test<b>foo</b>bar<b>bing</b>bang
+
+boo</div>',
+'<div><p>Test<b>foo</b>bar<b>bing</b>bang</p>
+
+<p>boo</p></div>'
+);
+ }
+
+ function testEmptyTokenAtEndOfDiv() {
+ $this->assertResult(
+'<div><p>foo</p>
+</div>',
+'<div><p>foo</p>
+</div>'
+);
+ }
+
+ function testEmptyDoubleLineTokenAtEndOfDiv() {
+ $this->assertResult(
+'<div><p>foo</p>
+
+</div>',
+'<div><p>foo</p>
+
+</div>'
+);
+ }
+
+ function testTextState11Root() {
+ $this->assertResult('<div></div> ');
+ }
+
+ function testTextState11Element() {
+ $this->assertResult(
+"<div><div></div>
+
+</div>");
+ }
+
+ function testTextStateLikeElementState111NoWhitespace() {
+ $this->assertResult('<div><p>P</p>Boo</div>', '<div><p>P</p>Boo</div>');
+ }
+
+ function testElementState111NoWhitespace() {
+ $this->assertResult('<div><p>P</p><b>Boo</b></div>', '<div><p>P</p><b>Boo</b></div>');
+ }
+
+ function testElementState133() {
+ $this->assertResult(
+"<div><b>B</b><pre>Ba</pre>
+
+Bar</div>",
+"<div><b>B</b><pre>Ba</pre>
+
+<p>Bar</p></div>"
+);
+ }
+
+ function testElementState22() {
+ $this->assertResult(
+ '<ul><li>foo</li></ul>'
+ );
+ }
+
+ function testElementState311() {
+ $this->assertResult(
+ '<p>Foo</p><b>Bar</b>',
+'<p>Foo</p>
+
+<p><b>Bar</b></p>'
+ );
+ }
+
+ function testAutoClose() {
+ $this->assertResult(
+ '<p></p>
+<hr />'
+ );
+ }
+
+ function testErrorNeeded() {
+ $this->config->set('HTML.Allowed', 'b');
+ $this->expectError('Cannot enable AutoParagraph injector because p is not allowed');
+ $this->assertResult('<b>foobar</b>');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Injector/DisplayLinkURITest.php b/lib/htmlpurifier/tests/HTMLPurifier/Injector/DisplayLinkURITest.php
new file mode 100644
index 000000000..1629d9bd1
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Injector/DisplayLinkURITest.php
@@ -0,0 +1,33 @@
+<?php
+
+class HTMLPurifier_Injector_DisplayLinkURITest extends HTMLPurifier_InjectorHarness
+{
+
+ function setup() {
+ parent::setup();
+ $this->config->set('AutoFormat.DisplayLinkURI', true);
+ }
+
+ function testBasicLink() {
+ $this->assertResult(
+ '<a href="http://malware.example.com">Don\'t go here!</a>',
+ '<a>Don\'t go here!</a> (http://malware.example.com)'
+ );
+ }
+
+ function testEmptyLink() {
+ $this->assertResult(
+ '<a>Don\'t go here!</a>',
+ '<a>Don\'t go here!</a>'
+ );
+ }
+ function testEmptyText() {
+ $this->assertResult(
+ '<a href="http://malware.example.com"></a>',
+ '<a></a> (http://malware.example.com)'
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Injector/LinkifyTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Injector/LinkifyTest.php
new file mode 100644
index 000000000..1a1542d53
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Injector/LinkifyTest.php
@@ -0,0 +1,50 @@
+<?php
+
+class HTMLPurifier_Injector_LinkifyTest extends HTMLPurifier_InjectorHarness
+{
+
+ function setup() {
+ parent::setup();
+ $this->config->set('AutoFormat.Linkify', true);
+ }
+
+ function testLinkifyURLInRootNode() {
+ $this->assertResult(
+ 'http://example.com',
+ '<a href="http://example.com">http://example.com</a>'
+ );
+ }
+
+ function testLinkifyURLInInlineNode() {
+ $this->assertResult(
+ '<b>http://example.com</b>',
+ '<b><a href="http://example.com">http://example.com</a></b>'
+ );
+ }
+
+ function testBasicUsageCase() {
+ $this->assertResult(
+ 'This URL http://example.com is what you need',
+ 'This URL <a href="http://example.com">http://example.com</a> is what you need'
+ );
+ }
+
+ function testIgnoreURLInATag() {
+ $this->assertResult(
+ '<a>http://example.com/</a>'
+ );
+ }
+
+ function testNeeded() {
+ $this->config->set('HTML.Allowed', 'b');
+ $this->expectError('Cannot enable Linkify injector because a is not allowed');
+ $this->assertResult('http://example.com/');
+ }
+
+ function testExcludes() {
+ $this->assertResult('<a><span>http://example.com</span></a>');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Injector/PurifierLinkifyTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Injector/PurifierLinkifyTest.php
new file mode 100644
index 000000000..774b234ad
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Injector/PurifierLinkifyTest.php
@@ -0,0 +1,59 @@
+<?php
+
+class HTMLPurifier_Injector_PurifierLinkifyTest extends HTMLPurifier_InjectorHarness
+{
+
+ function setup() {
+ parent::setup();
+ $this->config->set('AutoFormat.PurifierLinkify', true);
+ $this->config->set('AutoFormat.PurifierLinkify.DocURL', '#%s');
+ }
+
+ function testNoTriggerCharacer() {
+ $this->assertResult('Foobar');
+ }
+
+ function testTriggerCharacterInIrrelevantContext() {
+ $this->assertResult('20% off!');
+ }
+
+ function testPreserveNamespace() {
+ $this->assertResult('%Core namespace (not recognized)');
+ }
+
+ function testLinkifyBasic() {
+ $this->assertResult(
+ '%Namespace.Directive',
+ '<a href="#Namespace.Directive">%Namespace.Directive</a>'
+ );
+ }
+
+ function testLinkifyWithAdjacentTextNodes() {
+ $this->assertResult(
+ 'This %Namespace.Directive thing',
+ 'This <a href="#Namespace.Directive">%Namespace.Directive</a> thing'
+ );
+ }
+
+ function testLinkifyInBlock() {
+ $this->assertResult(
+ '<div>This %Namespace.Directive thing</div>',
+ '<div>This <a href="#Namespace.Directive">%Namespace.Directive</a> thing</div>'
+ );
+ }
+
+ function testPreserveInATag() {
+ $this->assertResult(
+ '<a>%Namespace.Directive</a>'
+ );
+ }
+
+ function testNeeded() {
+ $this->config->set('HTML.Allowed', 'b');
+ $this->expectError('Cannot enable PurifierLinkify injector because a is not allowed');
+ $this->assertResult('%Namespace.Directive');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Injector/RemoveEmptyTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Injector/RemoveEmptyTest.php
new file mode 100644
index 000000000..34dbc9515
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Injector/RemoveEmptyTest.php
@@ -0,0 +1,80 @@
+<?php
+
+class HTMLPurifier_Injector_RemoveEmptyTest extends HTMLPurifier_InjectorHarness
+{
+
+ public function setup() {
+ parent::setup();
+ $this->config->set('AutoFormat.RemoveEmpty', true);
+ }
+
+ function testPreserve() {
+ $this->assertResult('<b>asdf</b>');
+ }
+
+ function testRemove() {
+ $this->assertResult('<b></b>', '');
+ }
+
+ function testRemoveWithSpace() {
+ $this->assertResult('<b> </b>', '');
+ }
+
+ function testRemoveWithAttr() {
+ $this->assertResult('<b class="asdf"></b>', '');
+ }
+
+ function testRemoveIdAndName() {
+ $this->assertResult('<a id="asdf" name="asdf"></a>', '');
+ }
+
+ function testPreserveColgroup() {
+ $this->assertResult('<colgroup></colgroup>');
+ }
+
+ function testPreserveId() {
+ $this->config->set('Attr.EnableID', true);
+ $this->assertResult('<a id="asdf"></a>');
+ }
+
+ function testPreserveName() {
+ $this->config->set('Attr.EnableID', true);
+ $this->assertResult('<a name="asdf"></a>');
+ }
+
+ function testRemoveNested() {
+ $this->assertResult('<b><i></i></b>', '');
+ }
+
+ function testRemoveNested2() {
+ $this->assertResult('<b><i><u></u></i></b>', '');
+ }
+
+ function testRemoveNested3() {
+ $this->assertResult('<b> <i> <u> </u> </i> </b>', '');
+ }
+
+ function testRemoveNbsp() {
+ $this->config->set('AutoFormat.RemoveEmpty.RemoveNbsp', true);
+ $this->assertResult('<b>&nbsp;</b>', '');
+ }
+
+ function testRemoveNbspMix() {
+ $this->config->set('AutoFormat.RemoveEmpty.RemoveNbsp', true);
+ $this->assertResult('<b>&nbsp; &nbsp;</b>', '');
+ }
+
+ function testDontRemoveNbsp() {
+ $this->config->set('AutoFormat.RemoveEmpty.RemoveNbsp', true);
+ $this->assertResult('<td>&nbsp;</b>', "<td>\xC2\xA0</td>");
+ }
+
+ function testRemoveNbspExceptionsSpecial() {
+ $this->config->set('AutoFormat.RemoveEmpty.RemoveNbsp', true);
+ $this->config->set('AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions', 'b');
+ $this->assertResult('<b>&nbsp;</b>', "<b>\xC2\xA0</b>");
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Injector/RemoveSpansWithoutAttributesTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Injector/RemoveSpansWithoutAttributesTest.php
new file mode 100755
index 000000000..704c1cd7b
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Injector/RemoveSpansWithoutAttributesTest.php
@@ -0,0 +1,99 @@
+<?php
+
+class HTMLPurifier_Injector_RemoveSpansWithoutAttributesTest extends HTMLPurifier_InjectorHarness
+{
+ function setup() {
+ parent::setup();
+ $this->config->set('HTML.Allowed', 'span[class],div,p,strong,em');
+ $this->config->set('AutoFormat.RemoveSpansWithoutAttributes', true);
+ }
+
+ function testSingleSpan() {
+ $this->assertResult(
+ '<span>foo</span>',
+ 'foo'
+ );
+ }
+
+ function testSingleSpanWithAttributes() {
+ $this->assertResult(
+ '<span class="bar">foo</span>',
+ '<span class="bar">foo</span>'
+ );
+ }
+
+ function testSingleNestedSpan() {
+ $this->assertResult(
+ '<p><span>foo</span></p>',
+ '<p>foo</p>'
+ );
+ }
+
+ function testSingleNestedSpanWithAttributes() {
+ $this->assertResult(
+ '<p><span class="bar">foo</span></p>',
+ '<p><span class="bar">foo</span></p>'
+ );
+ }
+
+
+ function testSpanWithChildren() {
+ $this->assertResult(
+ '<span>foo <strong>bar</strong> <em>baz</em></span>',
+ 'foo <strong>bar</strong> <em>baz</em>'
+ );
+ }
+
+ function testSpanWithSiblings() {
+ $this->assertResult(
+ '<p>before <span>inside</span> <strong>after</strong></p>',
+ '<p>before inside <strong>after</strong></p>'
+ );
+ }
+
+ function testNestedSpanWithSiblingsAndChildren() {
+ $this->assertResult(
+ '<p>a <span>b <em>c</em> d</span> e</p>',
+ '<p>a b <em>c</em> d e</p>'
+ );
+ }
+
+ function testNestedSpansWithoutAttributes() {
+ $this->assertResult(
+ '<span>one<span>two<span>three</span></span></span>',
+ 'onetwothree'
+ );
+ }
+
+ function testDeeplyNestedSpan() {
+ $this->assertResult(
+ '<div><div><div><span class="a">a <span>b</span> c</span></div></div></div>',
+ '<div><div><div><span class="a">a b c</span></div></div></div>'
+ );
+ }
+
+ function testSpanWithInvalidAttributes() {
+ $this->assertResult(
+ '<p><span snorkel buzzer="emu">foo</span></p>',
+ '<p>foo</p>'
+ );
+ }
+
+ function testNestedAlternateSpans() {
+ $this->assertResult(
+'<span>a <span class="x">b <span>c <span class="y">d <span>e <span class="z">f
+</span></span></span></span></span></span>',
+'a <span class="x">b c <span class="y">d e <span class="z">f
+</span></span></span>'
+ );
+ }
+
+ function testSpanWithSomeInvalidAttributes() {
+ $this->assertResult(
+ '<p><span buzzer="emu" class="bar">foo</span></p>',
+ '<p><span class="bar">foo</span></p>'
+ );
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Injector/SafeObjectTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Injector/SafeObjectTest.php
new file mode 100644
index 000000000..59e0493c3
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Injector/SafeObjectTest.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * This test is kinda weird, because it doesn't test the full safe object
+ * functionality, just a small section of it. Or maybe it's actually the right
+ * way.
+ */
+class HTMLPurifier_Injector_SafeObjectTest extends HTMLPurifier_InjectorHarness
+{
+
+ function setup() {
+ parent::setup();
+ // there is no AutoFormat.SafeObject directive
+ $this->config->set('AutoFormat.Custom', array(new HTMLPurifier_Injector_SafeObject()));
+ $this->config->set('HTML.Trusted', true);
+ }
+
+ function testPreserve() {
+ $this->assertResult(
+ '<b>asdf</b>'
+ );
+ }
+
+ function testRemoveStrayParam() {
+ $this->assertResult(
+ '<param />',
+ ''
+ );
+ }
+
+ function testEditObjectParam() {
+ $this->assertResult(
+ '<object></object>',
+ '<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object>'
+ );
+ }
+
+ function testIgnoreStrayParam() {
+ $this->assertResult(
+ '<object><param /></object>',
+ '<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object>'
+ );
+ }
+
+ function testIgnoreDuplicates() {
+ $this->assertResult(
+ '<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object>'
+ );
+ }
+
+ function testIgnoreBogusData() {
+ $this->assertResult(
+ '<object><param name="allowScriptAccess" value="always" /><param name="allowNetworking" value="always" /></object>',
+ '<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object>'
+ );
+ }
+
+ function testIgnoreInvalidData() {
+ $this->assertResult(
+ '<object><param name="foo" value="bar" /></object>',
+ '<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object>'
+ );
+ }
+
+ function testKeepValidData() {
+ $this->assertResult(
+ '<object><param name="movie" value="bar" /></object>',
+ '<object data="bar"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><param name="movie" value="bar" /></object>'
+ );
+ }
+
+ function testNested() {
+ $this->assertResult(
+ '<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><object></object></object>',
+ '<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object></object>'
+ );
+ }
+
+ function testNotActuallyNested() {
+ $this->assertResult(
+ '<object><p><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></p></object>',
+ '<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><p></p></object>'
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/InjectorHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/InjectorHarness.php
new file mode 100644
index 000000000..e3815175a
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/InjectorHarness.php
@@ -0,0 +1,13 @@
+<?php
+
+class HTMLPurifier_InjectorHarness extends HTMLPurifier_StrategyHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_Strategy_MakeWellFormed();
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/LanguageFactoryTest.php b/lib/htmlpurifier/tests/HTMLPurifier/LanguageFactoryTest.php
new file mode 100644
index 000000000..9fea24f42
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/LanguageFactoryTest.php
@@ -0,0 +1,70 @@
+<?php
+
+class HTMLPurifier_LanguageFactoryTest extends HTMLPurifier_Harness
+{
+
+ /**
+ * Protected reference of global factory we're testing.
+ */
+ protected $factory;
+
+ public function setUp() {
+ $this->factory = HTMLPurifier_LanguageFactory::instance();
+ parent::setUp();
+ }
+
+ function test() {
+
+ $this->config->set('Core.Language', 'en');
+ $language = $this->factory->create($this->config, $this->context);
+
+ $this->assertIsA($language, 'HTMLPurifier_Language');
+ $this->assertIdentical($language->code, 'en');
+
+ // lazy loading test
+ $this->assertIdentical(count($language->messages), 0);
+ $language->load();
+ $this->assertNotEqual(count($language->messages), 0);
+
+ }
+
+ function testFallback() {
+
+ $this->config->set('Core.Language', 'en-x-test');
+ $language = $this->factory->create($this->config, $this->context);
+
+ $this->assertIsA($language, 'HTMLPurifier_Language_en_x_test');
+ $this->assertIdentical($language->code, 'en-x-test');
+
+ $language->load();
+
+ // test overloaded message
+ $this->assertIdentical($language->getMessage('HTMLPurifier'), 'HTML Purifier X');
+
+ // test inherited message
+ $this->assertIdentical($language->getMessage('LanguageFactoryTest: Pizza'), 'Pizza');
+
+ }
+
+ function testFallbackWithNoClass() {
+ $this->config->set('Core.Language', 'en-x-testmini');
+ $language = $this->factory->create($this->config, $this->context);
+ $this->assertIsA($language, 'HTMLPurifier_Language');
+ $this->assertIdentical($language->code, 'en-x-testmini');
+ $language->load();
+ $this->assertIdentical($language->getMessage('HTMLPurifier'), 'HTML Purifier XNone');
+ $this->assertIdentical($language->getMessage('LanguageFactoryTest: Pizza'), 'Pizza');
+ $this->assertIdentical($language->error, false);
+ }
+
+ function testNoSuchLanguage() {
+ $this->config->set('Core.Language', 'en-x-testnone');
+ $language = $this->factory->create($this->config, $this->context);
+ $this->assertIsA($language, 'HTMLPurifier_Language');
+ $this->assertIdentical($language->code, 'en-x-testnone');
+ $this->assertIdentical($language->error, true);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/LanguageTest.php b/lib/htmlpurifier/tests/HTMLPurifier/LanguageTest.php
new file mode 100644
index 000000000..033b6aa71
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/LanguageTest.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @todo Fix usage of HTMLPurifier_Language->_loaded using something else
+ */
+class HTMLPurifier_LanguageTest extends HTMLPurifier_Harness
+{
+
+ protected $lang;
+
+ protected function generateEnLanguage() {
+ $factory = HTMLPurifier_LanguageFactory::instance();
+ $config = HTMLPurifier_Config::create(array('Core.Language' => 'en'));
+ $context = new HTMLPurifier_Context();
+ return $factory->create($config, $context);
+ }
+
+ function test_getMessage() {
+ $config = HTMLPurifier_Config::createDefault();
+ $context = new HTMLPurifier_Context();
+ $lang = new HTMLPurifier_Language($config, $context);
+ $lang->_loaded = true;
+ $lang->messages['HTMLPurifier'] = 'HTML Purifier';
+ $this->assertIdentical($lang->getMessage('HTMLPurifier'), 'HTML Purifier');
+ $this->assertIdentical($lang->getMessage('LanguageTest: Totally non-existent key'), '[LanguageTest: Totally non-existent key]');
+ }
+
+ function test_formatMessage() {
+ $config = HTMLPurifier_Config::createDefault();
+ $context = new HTMLPurifier_Context();
+ $lang = new HTMLPurifier_Language($config, $context);
+ $lang->_loaded = true;
+ $lang->messages['LanguageTest: Error'] = 'Error is $1 on line $2';
+ $this->assertIdentical($lang->formatMessage('LanguageTest: Error', array(1=>'fatal', 32)), 'Error is fatal on line 32');
+ }
+
+ function test_formatMessage_tokenParameter() {
+ $config = HTMLPurifier_Config::createDefault();
+ $context = new HTMLPurifier_Context();
+ $generator = new HTMLPurifier_Generator($config, $context); // replace with mock if this gets icky
+ $context->register('Generator', $generator);
+ $lang = new HTMLPurifier_Language($config, $context);
+ $lang->_loaded = true;
+ $lang->messages['LanguageTest: Element info'] = 'Element Token: $1.Name, $1.Serialized, $1.Compact, $1.Line';
+ $lang->messages['LanguageTest: Data info'] = 'Data Token: $1.Data, $1.Serialized, $1.Compact, $1.Line';
+ $this->assertIdentical($lang->formatMessage('LanguageTest: Element info',
+ array(1=>new HTMLPurifier_Token_Start('a', array('href'=>'http://example.com'), 18))),
+ 'Element Token: a, <a href="http://example.com">, <a>, 18');
+ $this->assertIdentical($lang->formatMessage('LanguageTest: Data info',
+ array(1=>new HTMLPurifier_Token_Text('data>', 23))),
+ 'Data Token: data>, data&gt;, data&gt;, 23');
+ }
+
+ function test_listify() {
+ $lang = $this->generateEnLanguage();
+ $this->assertEqual($lang->listify(array('Item')), 'Item');
+ $this->assertEqual($lang->listify(array('Item', 'Item2')), 'Item and Item2');
+ $this->assertEqual($lang->listify(array('Item', 'Item2', 'Item3')), 'Item, Item2 and Item3');
+ }
+
+ function test_formatMessage_arrayParameter() {
+ $lang = $this->generateEnLanguage();
+
+ $array = array('Item1', 'Item2', 'Item3');
+ $this->assertIdentical(
+ $lang->formatMessage('LanguageTest: List', array(1=>$array)),
+ 'Item1, Item2 and Item3'
+ );
+
+ $array = array('Key1' => 'Value1', 'Key2' => 'Value2');
+ $this->assertIdentical(
+ $lang->formatMessage('LanguageTest: Hash', array(1=>$array)),
+ 'Key1 and Key2; Value1 and Value2'
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/LengthTest.php b/lib/htmlpurifier/tests/HTMLPurifier/LengthTest.php
new file mode 100644
index 000000000..2968b6d33
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/LengthTest.php
@@ -0,0 +1,73 @@
+<?php
+
+class HTMLPurifier_LengthTest extends HTMLPurifier_Harness
+{
+
+ function testConstruct() {
+ $l = new HTMLPurifier_Length('23', 'in');
+ $this->assertIdentical($l->getN(), '23');
+ $this->assertIdentical($l->getUnit(), 'in');
+ }
+
+ function testMake() {
+ $l = HTMLPurifier_Length::make('+23.4in');
+ $this->assertIdentical($l->getN(), '+23.4');
+ $this->assertIdentical($l->getUnit(), 'in');
+ }
+
+ function testToString() {
+ $l = new HTMLPurifier_Length('23', 'in');
+ $this->assertIdentical($l->toString(), '23in');
+ }
+
+ protected function assertValidate($string, $expect = true) {
+ if ($expect === true) $expect = $string;
+ $l = HTMLPurifier_Length::make($string);
+ $result = $l->isValid();
+ if ($result === false) $this->assertIdentical($expect, false);
+ else $this->assertIdentical($l->toString(), $expect);
+ }
+
+ function testValidate() {
+ $this->assertValidate('0');
+ $this->assertValidate('+0', '0');
+ $this->assertValidate('-0', '0');
+ $this->assertValidate('0px');
+ $this->assertValidate('4.5px');
+ $this->assertValidate('-4.5px');
+ $this->assertValidate('3ex');
+ $this->assertValidate('3em');
+ $this->assertValidate('3in');
+ $this->assertValidate('3cm');
+ $this->assertValidate('3mm');
+ $this->assertValidate('3pt');
+ $this->assertValidate('3pc');
+ $this->assertValidate('3PX', '3px');
+ $this->assertValidate('3', false);
+ $this->assertValidate('3miles', false);
+ }
+
+ /**
+ * @param $s1 First string to compare
+ * @param $s2 Second string to compare
+ * @param $expect 0 for $s1 == $s2, 1 for $s1 > $s2 and -1 for $s1 < $s2
+ */
+ protected function assertComparison($s1, $s2, $expect = 0) {
+ $l1 = HTMLPurifier_Length::make($s1);
+ $l2 = HTMLPurifier_Length::make($s2);
+ $r1 = $l1->compareTo($l2);
+ $r2 = $l2->compareTo($l1);
+ $this->assertIdentical($r1 == 0 ? 0 : ($r1 > 0 ? 1 : -1), $expect);
+ $this->assertIdentical($r2 == 0 ? 0 : ($r2 > 0 ? 1 : -1), - $expect);
+ }
+
+ function testCompareTo() {
+ $this->assertComparison('12in', '12in');
+ $this->assertComparison('12in', '12mm', 1);
+ $this->assertComparison('1px', '1mm', -1);
+ $this->assertComparison(str_repeat('2', 38) . 'in', '100px', 1);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Lexer/DirectLexTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Lexer/DirectLexTest.php
new file mode 100644
index 000000000..095b5c952
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Lexer/DirectLexTest.php
@@ -0,0 +1,130 @@
+<?php
+
+class HTMLPurifier_Lexer_DirectLexTest extends HTMLPurifier_Harness
+{
+
+ protected $DirectLex;
+
+ function setUp() {
+ $this->DirectLex = new HTMLPurifier_Lexer_DirectLex();
+ }
+
+ // internals testing
+ function test_parseAttributeString() {
+
+ $input[0] = 'href="about:blank" rel="nofollow"';
+ $expect[0] = array('href'=>'about:blank', 'rel'=>'nofollow');
+
+ $input[1] = "href='about:blank'";
+ $expect[1] = array('href'=>'about:blank');
+
+ // note that the single quotes aren't /really/ escaped
+ $input[2] = 'onclick="javascript:alert(\'asdf\');"';
+ $expect[2] = array('onclick' => "javascript:alert('asdf');");
+
+ $input[3] = 'selected';
+ $expect[3] = array('selected'=>'selected');
+
+ // [INVALID]
+ $input[4] = '="nokey"';
+ $expect[4] = array();
+
+ // [SIMPLE]
+ $input[5] = 'color=blue';
+ $expect[5] = array('color' => 'blue');
+
+ // [INVALID]
+ $input[6] = 'href="about:blank';
+ $expect[6] = array('href' => 'about:blank');
+
+ // [INVALID]
+ $input[7] = '"=';
+ $expect[7] = array('"' => '');
+ // we ought to get array()
+
+ $input[8] = 'href ="about:blank"rel ="nofollow"';
+ $expect[8] = array('href' => 'about:blank', 'rel' => 'nofollow');
+
+ $input[9] = 'two bool';
+ $expect[9] = array('two' => 'two', 'bool' => 'bool');
+
+ $input[10] = 'name="input" selected';
+ $expect[10] = array('name' => 'input', 'selected' => 'selected');
+
+ $input[11] = '=""';
+ $expect[11] = array();
+
+ $input[12] = '="" =""';
+ $expect[12] = array('"' => ''); // tough to say, just don't throw a loop
+
+ $input[13] = 'href="';
+ $expect[13] = array('href' => '');
+
+ $input[14] = 'href=" <';
+ $expect[14] = array('href' => ' <');
+
+ $config = HTMLPurifier_Config::createDefault();
+ $context = new HTMLPurifier_Context();
+ $size = count($input);
+ for($i = 0; $i < $size; $i++) {
+ $result = $this->DirectLex->parseAttributeString($input[$i], $config, $context);
+ $this->assertIdentical($expect[$i], $result, 'Test ' . $i . ': %s');
+ }
+
+ }
+
+ function testLineNumbers() {
+
+ // . . . . . . . . . .
+ // 01234567890123 01234567890123 0123456789012345 0123456789012 012345
+ $html = "<b>Line 1</b>\n<i>Line 2</i>\nStill Line 2<br\n/>Now Line 4\n\n<br />";
+
+ $expect = array(
+ // line 1
+ 0 => new HTMLPurifier_Token_Start('b')
+ ,1 => new HTMLPurifier_Token_Text('Line 1')
+ ,2 => new HTMLPurifier_Token_End('b')
+ ,3 => new HTMLPurifier_Token_Text("\n")
+ // line 2
+ ,4 => new HTMLPurifier_Token_Start('i')
+ ,5 => new HTMLPurifier_Token_Text('Line 2')
+ ,6 => new HTMLPurifier_Token_End('i')
+ ,7 => new HTMLPurifier_Token_Text("\nStill Line 2")
+ // line 3
+ ,8 => new HTMLPurifier_Token_Empty('br')
+ // line 4
+ ,9 => new HTMLPurifier_Token_Text("Now Line 4\n\n")
+ // line SIX
+ ,10 => new HTMLPurifier_Token_Empty('br')
+ );
+
+ $context = new HTMLPurifier_Context();
+ $config = HTMLPurifier_Config::createDefault();
+ $output = $this->DirectLex->tokenizeHTML($html, $config, $context);
+
+ $this->assertIdentical($output, $expect);
+
+ $context = new HTMLPurifier_Context();
+ $config = HTMLPurifier_Config::create(array(
+ 'Core.MaintainLineNumbers' => true
+ ));
+ $expect[0]->position(1, 0);
+ $expect[1]->position(1, 3);
+ $expect[2]->position(1, 9);
+ $expect[3]->position(2, -1);
+ $expect[4]->position(2, 0);
+ $expect[5]->position(2, 3);
+ $expect[6]->position(2, 9);
+ $expect[7]->position(3, -1);
+ $expect[8]->position(3, 12);
+ $expect[9]->position(4, 2);
+ $expect[10]->position(6, 0);
+
+ $output = $this->DirectLex->tokenizeHTML($html, $config, $context);
+ $this->assertIdentical($output, $expect);
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Lexer/DirectLex_ErrorsTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Lexer/DirectLex_ErrorsTest.php
new file mode 100644
index 000000000..69d3c628d
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Lexer/DirectLex_ErrorsTest.php
@@ -0,0 +1,58 @@
+<?php
+
+class HTMLPurifier_Lexer_DirectLex_ErrorsTest extends HTMLPurifier_ErrorsHarness
+{
+
+ function invoke($input) {
+ $lexer = new HTMLPurifier_Lexer_DirectLex();
+ $lexer->tokenizeHTML($input, $this->config, $this->context);
+ }
+
+ function invokeAttr($input) {
+ $lexer = new HTMLPurifier_Lexer_DirectLex();
+ $lexer->parseAttributeString($input, $this->config, $this->context);
+ }
+
+ function testExtractBody() {
+ $this->expectErrorCollection(E_WARNING, 'Lexer: Extracted body');
+ $this->invoke('<body>foo</body>');
+ }
+
+ function testUnclosedComment() {
+ $this->expectErrorCollection(E_WARNING, 'Lexer: Unclosed comment');
+ $this->expectContext('CurrentLine', 1);
+ $this->invoke('<!-- >');
+ }
+
+ function testUnescapedLt() {
+ $this->expectErrorCollection(E_NOTICE, 'Lexer: Unescaped lt');
+ $this->expectContext('CurrentLine', 1);
+ $this->invoke('< foo>');
+ }
+
+ function testMissingGt() {
+ $this->expectErrorCollection(E_WARNING, 'Lexer: Missing gt');
+ $this->expectContext('CurrentLine', 1);
+ $this->invoke('<a href=""');
+ }
+
+ // these are sub-errors, will only be thrown in context of collector
+
+ function testMissingAttributeKey1() {
+ $this->expectErrorCollection(E_ERROR, 'Lexer: Missing attribute key');
+ $this->invokeAttr('=""');
+ }
+
+ function testMissingAttributeKey2() {
+ $this->expectErrorCollection(E_ERROR, 'Lexer: Missing attribute key');
+ $this->invokeAttr('foo="bar" =""');
+ }
+
+ function testMissingEndQuote() {
+ $this->expectErrorCollection(E_ERROR, 'Lexer: Missing end quote');
+ $this->invokeAttr('src="foo');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/LexerTest.php b/lib/htmlpurifier/tests/HTMLPurifier/LexerTest.php
new file mode 100644
index 000000000..42a59aeb6
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/LexerTest.php
@@ -0,0 +1,752 @@
+<?php
+
+class HTMLPurifier_LexerTest extends HTMLPurifier_Harness
+{
+
+ protected $_has_pear = false;
+
+ public function __construct() {
+ parent::__construct();
+ if ($GLOBALS['HTMLPurifierTest']['PH5P']) {
+ require_once 'HTMLPurifier/Lexer/PH5P.php';
+ }
+ }
+
+ // HTMLPurifier_Lexer::create() --------------------------------------------
+
+ function test_create() {
+ $this->config->set('Core.MaintainLineNumbers', true);
+ $lexer = HTMLPurifier_Lexer::create($this->config);
+ $this->assertIsA($lexer, 'HTMLPurifier_Lexer_DirectLex');
+ }
+
+ function test_create_objectLexerImpl() {
+ $this->config->set('Core.LexerImpl', new HTMLPurifier_Lexer_DirectLex());
+ $lexer = HTMLPurifier_Lexer::create($this->config);
+ $this->assertIsA($lexer, 'HTMLPurifier_Lexer_DirectLex');
+ }
+
+ function test_create_unknownLexer() {
+ $this->config->set('Core.LexerImpl', 'AsdfAsdf');
+ $this->expectException(new HTMLPurifier_Exception('Cannot instantiate unrecognized Lexer type AsdfAsdf'));
+ HTMLPurifier_Lexer::create($this->config);
+ }
+
+ function test_create_incompatibleLexer() {
+ $this->config->set('Core.LexerImpl', 'DOMLex');
+ $this->config->set('Core.MaintainLineNumbers', true);
+ $this->expectException(new HTMLPurifier_Exception('Cannot use lexer that does not support line numbers with Core.MaintainLineNumbers or Core.CollectErrors (use DirectLex instead)'));
+ HTMLPurifier_Lexer::create($this->config);
+ }
+
+ // HTMLPurifier_Lexer->parseData() -----------------------------------------
+
+ function assertParseData($input, $expect = true) {
+ if ($expect === true) $expect = $input;
+ $lexer = new HTMLPurifier_Lexer();
+ $this->assertIdentical($expect, $lexer->parseData($input));
+ }
+
+ function test_parseData_plainText() {
+ $this->assertParseData('asdf');
+ }
+
+ function test_parseData_ampersandEntity() {
+ $this->assertParseData('&amp;', '&');
+ }
+
+ function test_parseData_quotEntity() {
+ $this->assertParseData('&quot;', '"');
+ }
+
+ function test_parseData_aposNumericEntity() {
+ $this->assertParseData('&#039;', "'");
+ }
+
+ function test_parseData_aposCompactNumericEntity() {
+ $this->assertParseData('&#39;', "'");
+ }
+
+ function test_parseData_adjacentAmpersandEntities() {
+ $this->assertParseData('&amp;&amp;&amp;', '&&&');
+ }
+
+ function test_parseData_trailingUnescapedAmpersand() {
+ $this->assertParseData('&amp;&', '&&');
+ }
+
+ function test_parseData_internalUnescapedAmpersand() {
+ $this->assertParseData('Procter & Gamble');
+ }
+
+ function test_parseData_improperEntityFaultToleranceTest() {
+ $this->assertParseData('&#x2D;');
+ }
+
+ // HTMLPurifier_Lexer->extractBody() ---------------------------------------
+
+ function assertExtractBody($text, $extract = true) {
+ $lexer = new HTMLPurifier_Lexer();
+ $result = $lexer->extractBody($text);
+ if ($extract === true) $extract = $text;
+ $this->assertIdentical($extract, $result);
+ }
+
+ function test_extractBody_noBodyTags() {
+ $this->assertExtractBody('<b>Bold</b>');
+ }
+
+ function test_extractBody_lowercaseBodyTags() {
+ $this->assertExtractBody('<html><body><b>Bold</b></body></html>', '<b>Bold</b>');
+ }
+
+ function test_extractBody_uppercaseBodyTags() {
+ $this->assertExtractBody('<HTML><BODY><B>Bold</B></BODY></HTML>', '<B>Bold</B>');
+ }
+
+ function test_extractBody_realisticUseCase() {
+ $this->assertExtractBody(
+'<?xml version="1.0"
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <title>xyz</title>
+ </head>
+ <body>
+ <form method="post" action="whatever1">
+ <div>
+ <input type="text" name="username" />
+ <input type="text" name="password" />
+ <input type="submit" />
+ </div>
+ </form>
+ </body>
+</html>',
+ '
+ <form method="post" action="whatever1">
+ <div>
+ <input type="text" name="username" />
+ <input type="text" name="password" />
+ <input type="submit" />
+ </div>
+ </form>
+ ');
+ }
+
+ function test_extractBody_bodyWithAttributes() {
+ $this->assertExtractBody('<html><body bgcolor="#F00"><b>Bold</b></body></html>', '<b>Bold</b>');
+ }
+
+ function test_extractBody_preserveUnclosedBody() {
+ $this->assertExtractBody('<body>asdf'); // not closed, don't accept
+ }
+
+ function test_extractBody_useLastBody() {
+ $this->assertExtractBody('<body>foo</body>bar</body>', 'foo</body>bar');
+ }
+
+ // HTMLPurifier_Lexer->tokenizeHTML() --------------------------------------
+
+ function assertTokenization($input, $expect, $alt_expect = array()) {
+ $lexers = array();
+ $lexers['DirectLex'] = new HTMLPurifier_Lexer_DirectLex();
+ if (class_exists('DOMDocument')) {
+ $lexers['DOMLex'] = new HTMLPurifier_Lexer_DOMLex();
+ $lexers['PH5P'] = new HTMLPurifier_Lexer_PH5P();
+ }
+ foreach ($lexers as $name => $lexer) {
+ $result = $lexer->tokenizeHTML($input, $this->config, $this->context);
+ if (isset($alt_expect[$name])) {
+ if ($alt_expect[$name] === false) continue;
+ $t_expect = $alt_expect[$name];
+ $this->assertIdentical($result, $alt_expect[$name], "$name: %s");
+ } else {
+ $t_expect = $expect;
+ $this->assertIdentical($result, $expect, "$name: %s");
+ }
+ if ($t_expect != $result) {
+ printTokens($result);
+ }
+ }
+ }
+
+ function test_tokenizeHTML_emptyInput() {
+ $this->assertTokenization('', array());
+ }
+
+ function test_tokenizeHTML_plainText() {
+ $this->assertTokenization(
+ 'This is regular text.',
+ array(
+ new HTMLPurifier_Token_Text('This is regular text.')
+ )
+ );
+ }
+
+ function test_tokenizeHTML_textAndTags() {
+ $this->assertTokenization(
+ 'This is <b>bold</b> text',
+ array(
+ new HTMLPurifier_Token_Text('This is '),
+ new HTMLPurifier_Token_Start('b', array()),
+ new HTMLPurifier_Token_Text('bold'),
+ new HTMLPurifier_Token_End('b'),
+ new HTMLPurifier_Token_Text(' text'),
+ )
+ );
+ }
+
+ function test_tokenizeHTML_normalizeCase() {
+ $this->assertTokenization(
+ '<DIV>Totally rad dude. <b>asdf</b></div>',
+ array(
+ new HTMLPurifier_Token_Start('DIV', array()),
+ new HTMLPurifier_Token_Text('Totally rad dude. '),
+ new HTMLPurifier_Token_Start('b', array()),
+ new HTMLPurifier_Token_Text('asdf'),
+ new HTMLPurifier_Token_End('b'),
+ new HTMLPurifier_Token_End('div'),
+ )
+ );
+ }
+
+ function test_tokenizeHTML_notWellFormed() {
+ $this->assertTokenization(
+ '<asdf></asdf><d></d><poOloka><poolasdf><ds></asdf></ASDF>',
+ array(
+ new HTMLPurifier_Token_Start('asdf'),
+ new HTMLPurifier_Token_End('asdf'),
+ new HTMLPurifier_Token_Start('d'),
+ new HTMLPurifier_Token_End('d'),
+ new HTMLPurifier_Token_Start('poOloka'),
+ new HTMLPurifier_Token_Start('poolasdf'),
+ new HTMLPurifier_Token_Start('ds'),
+ new HTMLPurifier_Token_End('asdf'),
+ new HTMLPurifier_Token_End('ASDF'),
+ ),
+ array(
+ 'DOMLex' => $alt = array(
+ new HTMLPurifier_Token_Empty('asdf'),
+ new HTMLPurifier_Token_Empty('d'),
+ new HTMLPurifier_Token_Start('pooloka'),
+ new HTMLPurifier_Token_Start('poolasdf'),
+ new HTMLPurifier_Token_Empty('ds'),
+ new HTMLPurifier_Token_End('poolasdf'),
+ new HTMLPurifier_Token_End('pooloka'),
+ ),
+ 'PH5P' => $alt,
+ )
+ );
+ }
+
+ function test_tokenizeHTML_whitespaceInTag() {
+ $this->assertTokenization(
+ '<a'."\t".'href="foobar.php"'."\n".'title="foo!">Link to <b id="asdf">foobar</b></a>',
+ array(
+ new HTMLPurifier_Token_Start('a',array('href'=>'foobar.php','title'=>'foo!')),
+ new HTMLPurifier_Token_Text('Link to '),
+ new HTMLPurifier_Token_Start('b',array('id'=>'asdf')),
+ new HTMLPurifier_Token_Text('foobar'),
+ new HTMLPurifier_Token_End('b'),
+ new HTMLPurifier_Token_End('a'),
+ )
+ );
+ }
+
+ function test_tokenizeHTML_singleAttribute() {
+ $this->assertTokenization(
+ '<br style="&amp;" />',
+ array(
+ new HTMLPurifier_Token_Empty('br', array('style' => '&'))
+ )
+ );
+ }
+
+ function test_tokenizeHTML_emptyTag() {
+ $this->assertTokenization(
+ '<br />',
+ array( new HTMLPurifier_Token_Empty('br') )
+ );
+ }
+
+ function test_tokenizeHTML_comment() {
+ $this->assertTokenization(
+ '<!-- Comment -->',
+ array( new HTMLPurifier_Token_Comment(' Comment ') )
+ );
+ }
+
+ function test_tokenizeHTML_malformedComment() {
+ $this->assertTokenization(
+ '<!-- not so well formed --->',
+ array( new HTMLPurifier_Token_Comment(' not so well formed -') )
+ );
+ }
+
+ function test_tokenizeHTML_unterminatedTag() {
+ $this->assertTokenization(
+ '<a href=""',
+ array( new HTMLPurifier_Token_Text('<a href=""') ),
+ array(
+ // I like our behavior better, but it's non-standard
+ 'DOMLex' => array( new HTMLPurifier_Token_Empty('a', array('href'=>'')) ),
+ 'PH5P' => false, // total barfing, grabs scaffolding too
+ )
+ );
+ }
+
+ function test_tokenizeHTML_specialEntities() {
+ $this->assertTokenization(
+ '&lt;b&gt;',
+ array(
+ new HTMLPurifier_Token_Text('<b>')
+ ),
+ array(
+ // some parsers will separate entities out
+ 'PH5P' => array(
+ new HTMLPurifier_Token_Text('<'),
+ new HTMLPurifier_Token_Text('b'),
+ new HTMLPurifier_Token_Text('>'),
+ ),
+ )
+ );
+ }
+
+ function test_tokenizeHTML_earlyQuote() {
+ $this->assertTokenization(
+ '<a "=>',
+ array( new HTMLPurifier_Token_Empty('a') ),
+ array(
+ // we barf on this input
+ 'DirectLex' => array(
+ new HTMLPurifier_Token_Start('a', array('"' => ''))
+ ),
+ 'PH5P' => false, // behavior varies; handle this personally
+ )
+ );
+ }
+
+ function test_tokenizeHTML_earlyQuote_PH5P() {
+ if (!class_exists('DOMDocument')) return;
+ $lexer = new HTMLPurifier_Lexer_PH5P();
+ $result = $lexer->tokenizeHTML('<a "=>', $this->config, $this->context);
+ if ($this->context->get('PH5PError', true)) {
+ $this->assertIdentical(array(
+ new HTMLPurifier_Token_Start('a', array('"' => ''))
+ ), $result);
+ } else {
+ $this->assertIdentical(array(
+ new HTMLPurifier_Token_Empty('a', array('"' => ''))
+ ), $result);
+ }
+ }
+
+ function test_tokenizeHTML_unescapedQuote() {
+ $this->assertTokenization(
+ '"',
+ array( new HTMLPurifier_Token_Text('"') )
+ );
+ }
+
+ function test_tokenizeHTML_escapedQuote() {
+ $this->assertTokenization(
+ '&quot;',
+ array( new HTMLPurifier_Token_Text('"') )
+ );
+ }
+
+ function test_tokenizeHTML_cdata() {
+ $this->assertTokenization(
+ '<![CDATA[You <b>can&#39;t</b> get me!]]>',
+ array( new HTMLPurifier_Token_Text('You <b>can&#39;t</b> get me!') ),
+ array(
+ 'PH5P' => array(
+ new HTMLPurifier_Token_Text('You '),
+ new HTMLPurifier_Token_Text('<'),
+ new HTMLPurifier_Token_Text('b'),
+ new HTMLPurifier_Token_Text('>'),
+ new HTMLPurifier_Token_Text('can'),
+ new HTMLPurifier_Token_Text('&'),
+ new HTMLPurifier_Token_Text('#39;t'),
+ new HTMLPurifier_Token_Text('<'),
+ new HTMLPurifier_Token_Text('/b'),
+ new HTMLPurifier_Token_Text('>'),
+ new HTMLPurifier_Token_Text(' get me!'),
+ ),
+ )
+ );
+ }
+
+ function test_tokenizeHTML_characterEntity() {
+ $this->assertTokenization(
+ '&theta;',
+ array( new HTMLPurifier_Token_Text("\xCE\xB8") )
+ );
+ }
+
+ function test_tokenizeHTML_characterEntityInCDATA() {
+ $this->assertTokenization(
+ '<![CDATA[&rarr;]]>',
+ array( new HTMLPurifier_Token_Text("&rarr;") ),
+ array(
+ 'PH5P' => array(
+ new HTMLPurifier_Token_Text('&'),
+ new HTMLPurifier_Token_Text('rarr;'),
+ ),
+ )
+ );
+ }
+
+ function test_tokenizeHTML_entityInAttribute() {
+ $this->assertTokenization(
+ '<a href="index.php?title=foo&amp;id=bar">Link</a>',
+ array(
+ new HTMLPurifier_Token_Start('a',array('href' => 'index.php?title=foo&id=bar')),
+ new HTMLPurifier_Token_Text('Link'),
+ new HTMLPurifier_Token_End('a'),
+ )
+ );
+ }
+
+ function test_tokenizeHTML_preserveUTF8() {
+ $this->assertTokenization(
+ "\xCE\xB8",
+ array( new HTMLPurifier_Token_Text("\xCE\xB8") )
+ );
+ }
+
+ function test_tokenizeHTML_specialEntityInAttribute() {
+ $this->assertTokenization(
+ '<br test="x &lt; 6" />',
+ array( new HTMLPurifier_Token_Empty('br', array('test' => 'x < 6')) )
+ );
+ }
+
+ function test_tokenizeHTML_emoticonProtection() {
+ $this->assertTokenization(
+ '<b>Whoa! <3 That\'s not good >.></b>',
+ array(
+ new HTMLPurifier_Token_Start('b'),
+ new HTMLPurifier_Token_Text('Whoa! '),
+ new HTMLPurifier_Token_Text('<'),
+ new HTMLPurifier_Token_Text('3 That\'s not good >.>'),
+ new HTMLPurifier_Token_End('b')
+ ),
+ array(
+ // text is absorbed together
+ 'DOMLex' => array(
+ new HTMLPurifier_Token_Start('b'),
+ new HTMLPurifier_Token_Text('Whoa! <3 That\'s not good >.>'),
+ new HTMLPurifier_Token_End('b'),
+ ),
+ 'PH5P' => array( // interesting grouping
+ new HTMLPurifier_Token_Start('b'),
+ new HTMLPurifier_Token_Text('Whoa! '),
+ new HTMLPurifier_Token_Text('<'),
+ new HTMLPurifier_Token_Text('3 That\'s not good >.>'),
+ new HTMLPurifier_Token_End('b'),
+ ),
+ )
+ );
+ }
+
+ function test_tokenizeHTML_commentWithFunkyChars() {
+ $this->assertTokenization(
+ '<!-- This >< comment --><br />',
+ array(
+ new HTMLPurifier_Token_Comment(' This >< comment '),
+ new HTMLPurifier_Token_Empty('br'),
+ )
+ );
+ }
+
+ function test_tokenizeHTML_unterminatedComment() {
+ $this->assertTokenization(
+ '<!-- This >< comment',
+ array( new HTMLPurifier_Token_Comment(' This >< comment') ),
+ array(
+ 'DOMLex' => false,
+ 'PH5P' => false,
+ )
+ );
+ }
+
+ function test_tokenizeHTML_scriptCDATAContents() {
+ $this->config->set('HTML.Trusted', true);
+ $this->assertTokenization(
+ 'Foo: <script>alert("<foo>");</script>',
+ array(
+ new HTMLPurifier_Token_Text('Foo: '),
+ new HTMLPurifier_Token_Start('script'),
+ new HTMLPurifier_Token_Text('alert("<foo>");'),
+ new HTMLPurifier_Token_End('script'),
+ ),
+ array(
+ // PH5P, for some reason, bubbles the script to <head>
+ 'PH5P' => false,
+ )
+ );
+ }
+
+ function test_tokenizeHTML_entitiesInComment() {
+ $this->assertTokenization(
+ '<!-- This comment < &lt; & -->',
+ array( new HTMLPurifier_Token_Comment(' This comment < &lt; & ') )
+ );
+ }
+
+ function test_tokenizeHTML_attributeWithSpecialCharacters() {
+ $this->assertTokenization(
+ '<a href="><>">',
+ array( new HTMLPurifier_Token_Empty('a', array('href' => '><>')) ),
+ array(
+ 'DirectLex' => array(
+ new HTMLPurifier_Token_Start('a', array('href' => '')),
+ new HTMLPurifier_Token_Text('<'),
+ new HTMLPurifier_Token_Text('">'),
+ )
+ )
+ );
+ }
+
+ function test_tokenizeHTML_emptyTagWithSlashInAttribute() {
+ $this->assertTokenization(
+ '<param name="src" value="http://example.com/video.wmv" />',
+ array( new HTMLPurifier_Token_Empty('param', array('name' => 'src', 'value' => 'http://example.com/video.wmv')) )
+ );
+ }
+
+ function test_tokenizeHTML_style() {
+ $extra = array(
+ // PH5P doesn't seem to like style tags
+ 'PH5P' => false,
+ // DirectLex defers to RemoveForeignElements for textification
+ 'DirectLex' => array(
+ new HTMLPurifier_Token_Start('style', array('type' => 'text/css')),
+ new HTMLPurifier_Token_Comment("\ndiv {}\n"),
+ new HTMLPurifier_Token_End('style'),
+ ),
+ );
+ if (!defined('LIBXML_VERSION')) {
+ // LIBXML_VERSION is missing in early versions of PHP
+ // prior to 1.30 of php-src/ext/libxml/libxml.c (version-wise,
+ // this translates to 5.0.x. In such cases, punt the test entirely.
+ return;
+ } elseif (LIBXML_VERSION < 20628) {
+ // libxml's behavior is wrong prior to this version, so make
+ // appropriate accomodations
+ $extra['DOMLex'] = $extra['DirectLex'];
+ }
+ $this->assertTokenization(
+'<style type="text/css"><!--
+div {}
+--></style>',
+ array(
+ new HTMLPurifier_Token_Start('style', array('type' => 'text/css')),
+ new HTMLPurifier_Token_Text("\ndiv {}\n"),
+ new HTMLPurifier_Token_End('style'),
+ ),
+ $extra
+ );
+ }
+
+ function test_tokenizeHTML_tagWithAtSignAndExtraGt() {
+ $alt_expect = array(
+ // Technically this is invalid, but it won't be a
+ // problem with invalid element removal; also, this
+ // mimics Mozilla's parsing of the tag.
+ new HTMLPurifier_Token_Start('a@'),
+ new HTMLPurifier_Token_Text('>'),
+ );
+ $this->assertTokenization(
+ '<a@>>',
+ array(
+ new HTMLPurifier_Token_Start('a'),
+ new HTMLPurifier_Token_Text('>'),
+ new HTMLPurifier_Token_End('a'),
+ ),
+ array(
+ 'DirectLex' => $alt_expect,
+ )
+ );
+ }
+
+ function test_tokenizeHTML_emoticonHeart() {
+ $this->assertTokenization(
+ '<br /><3<br />',
+ array(
+ new HTMLPurifier_Token_Empty('br'),
+ new HTMLPurifier_Token_Text('<'),
+ new HTMLPurifier_Token_Text('3'),
+ new HTMLPurifier_Token_Empty('br'),
+ ),
+ array(
+ 'DOMLex' => array(
+ new HTMLPurifier_Token_Empty('br'),
+ new HTMLPurifier_Token_Text('<3'),
+ new HTMLPurifier_Token_Empty('br'),
+ ),
+ )
+ );
+ }
+
+ function test_tokenizeHTML_emoticonShiftyEyes() {
+ $this->assertTokenization(
+ '<b><<</b>',
+ array(
+ new HTMLPurifier_Token_Start('b'),
+ new HTMLPurifier_Token_Text('<'),
+ new HTMLPurifier_Token_Text('<'),
+ new HTMLPurifier_Token_End('b'),
+ ),
+ array(
+ 'DOMLex' => array(
+ new HTMLPurifier_Token_Start('b'),
+ new HTMLPurifier_Token_Text('<<'),
+ new HTMLPurifier_Token_End('b'),
+ ),
+ )
+ );
+ }
+
+ function test_tokenizeHTML_eon1996() {
+ $this->assertTokenization(
+ '< <b>test</b>',
+ array(
+ new HTMLPurifier_Token_Text('<'),
+ new HTMLPurifier_Token_Text(' '),
+ new HTMLPurifier_Token_Start('b'),
+ new HTMLPurifier_Token_Text('test'),
+ new HTMLPurifier_Token_End('b'),
+ ),
+ array(
+ 'DOMLex' => array(
+ new HTMLPurifier_Token_Text('< '),
+ new HTMLPurifier_Token_Start('b'),
+ new HTMLPurifier_Token_Text('test'),
+ new HTMLPurifier_Token_End('b'),
+ ),
+ )
+ );
+ }
+
+ function test_tokenizeHTML_bodyInCDATA() {
+ $alt_tokens = array(
+ new HTMLPurifier_Token_Text('<'),
+ new HTMLPurifier_Token_Text('body'),
+ new HTMLPurifier_Token_Text('>'),
+ new HTMLPurifier_Token_Text('Foo'),
+ new HTMLPurifier_Token_Text('<'),
+ new HTMLPurifier_Token_Text('/body'),
+ new HTMLPurifier_Token_Text('>'),
+ );
+ $this->assertTokenization(
+ '<![CDATA[<body>Foo</body>]]>',
+ array(
+ new HTMLPurifier_Token_Text('<body>Foo</body>'),
+ ),
+ array(
+ 'PH5P' => $alt_tokens,
+ )
+ );
+ }
+
+ function test_tokenizeHTML_() {
+ $this->assertTokenization(
+ '<a><img /></a>',
+ array(
+ new HTMLPurifier_Token_Start('a'),
+ new HTMLPurifier_Token_Empty('img'),
+ new HTMLPurifier_Token_End('a'),
+ )
+ );
+ }
+
+ function test_tokenizeHTML_ignoreIECondComment() {
+ $this->assertTokenization(
+ '<!--[if IE]>foo<a>bar<!-- baz --><![endif]-->',
+ array()
+ );
+ }
+
+ function test_tokenizeHTML_removeProcessingInstruction() {
+ $this->config->set('Core.RemoveProcessingInstructions', true);
+ $this->assertTokenization(
+ '<?xml blah blah ?>',
+ array()
+ );
+ }
+
+ function test_tokenizeHTML_removeNewline() {
+ $this->config->set('Core.NormalizeNewlines', true);
+ $this->assertTokenization(
+ "plain\rtext\r\n",
+ array(
+ new HTMLPurifier_Token_Text("plain\ntext\n")
+ )
+ );
+ }
+
+ function test_tokenizeHTML_noRemoveNewline() {
+ $this->config->set('Core.NormalizeNewlines', false);
+ $this->assertTokenization(
+ "plain\rtext\r\n",
+ array(
+ new HTMLPurifier_Token_Text("plain\rtext\r\n")
+ )
+ );
+ }
+
+ function test_tokenizeHTML_conditionalCommentUngreedy() {
+ $this->assertTokenization(
+ '<!--[if gte mso 9]>a<![endif]-->b<!--[if gte mso 9]>c<![endif]-->',
+ array(
+ new HTMLPurifier_Token_Text("b")
+ )
+ );
+ }
+
+ function test_tokenizeHTML_imgTag() {
+ $start = array(
+ new HTMLPurifier_Token_Start('img',
+ array(
+ 'src' => 'img_11775.jpg',
+ 'alt' => '[Img #11775]',
+ 'id' => 'EMBEDDED_IMG_11775',
+ )
+ )
+ );
+ $this->assertTokenization(
+ '<img src="img_11775.jpg" alt="[Img #11775]" id="EMBEDDED_IMG_11775" >',
+ array(
+ new HTMLPurifier_Token_Empty('img',
+ array(
+ 'src' => 'img_11775.jpg',
+ 'alt' => '[Img #11775]',
+ 'id' => 'EMBEDDED_IMG_11775',
+ )
+ )
+ ),
+ array(
+ 'DirectLex' => $start,
+ )
+ );
+ }
+
+
+ /*
+
+ function test_tokenizeHTML_() {
+ $this->assertTokenization(
+ ,
+ array(
+
+ )
+ );
+ }
+ */
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/domxml.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/domxml.phpt
new file mode 100644
index 000000000..406ac400b
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/domxml.phpt
@@ -0,0 +1,15 @@
+--TEST--
+DirectLex with domxml test
+--SKIPIF--
+<?php
+if (!extension_loaded('dom')) {
+ echo "skip - dom not available";
+} elseif (!extension_loaded('domxml')) {
+ echo "skip - domxml not loaded";
+}
+--FILE--
+<?php
+require '../library/HTMLPurifier.auto.php';
+echo get_class(HTMLPurifier_Lexer::create(HTMLPurifier_Config::createDefault()));
+--EXPECT--
+HTMLPurifier_Lexer_DirectLex \ No newline at end of file
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/func.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/func.phpt
new file mode 100644
index 000000000..d194d68d3
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/func.phpt
@@ -0,0 +1,9 @@
+--TEST--
+HTMLPurifier.func.php test
+--FILE--
+<?php
+require '../library/HTMLPurifier.auto.php';
+require 'HTMLPurifier.func.php';
+echo HTMLPurifier('<b>Salsa!');
+--EXPECT--
+<b>Salsa!</b> \ No newline at end of file
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/kses/basic.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/kses/basic.phpt
new file mode 100644
index 000000000..8a9cd0162
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/kses/basic.phpt
@@ -0,0 +1,15 @@
+--TEST--
+HTMLPurifier.kses.php basic test
+--FILE--
+<?php
+require '../library/HTMLPurifier.kses.php';
+echo kses(
+ '<a class="foo" style="color:#F00;" href="https://google.com">Foo<i>Bar</i>',
+ array(
+ 'a' => array('class' => 1, 'href' => 1),
+ ),
+ array('http') // no https!
+);
+
+--EXPECT--
+<a class="foo">FooBar</a> \ No newline at end of file
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/_autoload.inc b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/_autoload.inc
new file mode 100644
index 000000000..42e3500e4
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/_autoload.inc
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * Tests if autoloading for HTML Purifier is enabled. If all tests pass,
+ * output is blank.
+ */
+
+assert("!in_array(realpath('../library/HTMLPurifier/Filter/YouTube.php'), get_included_files())");
+new HTMLPurifier_Filter_YouTube();
+assert(" in_array(realpath('../library/HTMLPurifier.autoload.php'), get_included_files())");
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/_no-autoload.inc b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/_no-autoload.inc
new file mode 100644
index 000000000..fc109e727
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/_no-autoload.inc
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * Tests if autoloading is off in HTML Purifier. If all tests pass, no output.
+ */
+
+if (function_exists('spl_autoload_register')) {
+ $__v = spl_autoload_functions();
+ assert('$__v == false || !in_array(array("HTMLPurifier_Bootstrap", "autoload"), $__v)');
+} else {
+ if (function_exists('__autoload')) {
+ $__r = new ReflectionFunction('__autoload');
+ assert('$__r->getFileName() != realpath("../library/HTMLPurifier.autoload.php")');
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-includes.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-includes.phpt
new file mode 100644
index 000000000..6a8f909b8
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-includes.phpt
@@ -0,0 +1,12 @@
+--TEST--
+HTMLPurifier.auto.php and HTMLPurifier.includes.php loading test
+--FILE--
+<?php
+require '../library/HTMLPurifier.path.php';
+require 'HTMLPurifier.includes.php';
+require 'HTMLPurifier/PHPT/loading/_no-autoload.inc';
+$config = HTMLPurifier_Config::createDefault();
+$purifier = new HTMLPurifier($config);
+echo $purifier->purify('<b>Salsa!');
+--EXPECT--
+<b>Salsa!</b> \ No newline at end of file
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-autoload.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-autoload.phpt
new file mode 100644
index 000000000..aad437202
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-autoload.phpt
@@ -0,0 +1,28 @@
+--TEST--
+HTMLPurifier.auto.php using spl_autoload_register with __autoload() already defined loading test
+--SKIPIF--
+<?php
+if (!function_exists('spl_autoload_register')) {
+ echo "skip - spl_autoload_register() not available";
+}
+--FILE--
+<?php
+function __autoload($class) {
+ echo "Autoloading $class...
+";
+ eval("class $class {}");
+}
+
+require '../library/HTMLPurifier.auto.php';
+require 'HTMLPurifier/PHPT/loading/_autoload.inc';
+$config = HTMLPurifier_Config::createDefault();
+$purifier = new HTMLPurifier($config);
+echo $purifier->purify('<b>Salsa!') . "
+";
+
+// purposely invoke older autoload
+$bar = new Bar();
+
+--EXPECT--
+<b>Salsa!</b>
+Autoloading Bar... \ No newline at end of file
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-spl-autoload-default.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-spl-autoload-default.phpt
new file mode 100644
index 000000000..a4011f1d3
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-spl-autoload-default.phpt
@@ -0,0 +1,25 @@
+--TEST--
+HTMLPurifier.auto.php using spl_autoload_register default
+--SKIPIF--
+<?php
+if (!function_exists('spl_autoload_register')) {
+ echo "skip - spl_autoload_register() not available";
+}
+--FILE--
+<?php
+spl_autoload_extensions(".php");
+spl_autoload_register();
+
+require '../library/HTMLPurifier.auto.php';
+require 'HTMLPurifier/PHPT/loading/_autoload.inc';
+$config = HTMLPurifier_Config::createDefault();
+$purifier = new HTMLPurifier($config);
+echo $purifier->purify('<b>Salsa!') . "
+";
+
+// purposely invoke standard autoload
+$test = new default_load();
+
+--EXPECT--
+<b>Salsa!</b>
+Default loaded
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-spl-autoload.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-spl-autoload.phpt
new file mode 100644
index 000000000..1697bb13f
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-spl-autoload.phpt
@@ -0,0 +1,43 @@
+--TEST--
+HTMLPurifier.auto.php using spl_autoload_register with user registration loading test
+--SKIPIF--
+<?php
+if (!function_exists('spl_autoload_register')) {
+ echo "skip - spl_autoload_register() not available";
+}
+--FILE--
+<?php
+function my_autoload($class) {
+ echo "Autoloading $class...
+";
+ eval("class $class {}");
+ return true;
+}
+class MyClass {
+ public static function myAutoload($class) {
+ if ($class == 'Foo') {
+ echo "Special autoloading Foo...
+";
+ eval("class $class {}");
+ }
+ }
+}
+
+spl_autoload_register(array('MyClass', 'myAutoload'));
+spl_autoload_register('my_autoload');
+
+require '../library/HTMLPurifier.auto.php';
+require 'HTMLPurifier/PHPT/loading/_autoload.inc';
+$config = HTMLPurifier_Config::createDefault();
+$purifier = new HTMLPurifier($config);
+echo $purifier->purify('<b>Salsa!') . "
+";
+
+// purposely invoke older autoloads
+$foo = new Foo();
+$bar = new Bar();
+
+--EXPECT--
+<b>Salsa!</b>
+Special autoloading Foo...
+Autoloading Bar... \ No newline at end of file
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-without-spl-autoload.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-without-spl-autoload.phpt
new file mode 100644
index 000000000..aeee9dcee
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-without-spl-autoload.phpt
@@ -0,0 +1,19 @@
+--TEST--
+HTMLPurifier.auto.php without spl_autoload_register without userland autoload loading test
+--SKIPIF--
+<?php
+if (function_exists('spl_autoload_register')) {
+ echo "skip - spl_autoload_register() available";
+}
+--FILE--
+<?php
+assert("!function_exists('__autoload')");
+require '../library/HTMLPurifier.auto.php';
+require 'HTMLPurifier/PHPT/loading/_autoload.inc';
+$config = HTMLPurifier_Config::createDefault();
+$purifier = new HTMLPurifier($config);
+echo $purifier->purify('<b>Salsa!') . "
+";
+
+--EXPECT--
+<b>Salsa!</b> \ No newline at end of file
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-without-spl-with-autoload.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-without-spl-with-autoload.phpt
new file mode 100644
index 000000000..2b6f49d8c
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-without-spl-with-autoload.phpt
@@ -0,0 +1,21 @@
+--TEST--
+HTMLPurifier.auto.php without spl_autoload_register but with userland
+__autoload() defined test
+--SKIPIF--
+<?php
+if (function_exists('spl_autoload_register')) {
+ echo "skip - spl_autoload_register() available";
+}
+--FILE--
+<?php
+function __autoload($class) {
+ echo "Autoloading $class...
+";
+ eval("class $class {}");
+}
+require '../library/HTMLPurifier.auto.php';
+require 'HTMLPurifier/PHPT/loading/_no-autoload.inc';
+$purifier = new HTMLPurifier();
+
+--EXPECT--
+Autoloading HTMLPurifier... \ No newline at end of file
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto.phpt
new file mode 100644
index 000000000..a4ea716f9
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto.phpt
@@ -0,0 +1,11 @@
+--TEST--
+HTMLPurifier.auto.php loading test
+--FILE--
+<?php
+require '../library/HTMLPurifier.auto.php';
+require 'HTMLPurifier/PHPT/loading/_autoload.inc';
+$config = HTMLPurifier_Config::createDefault();
+$purifier = new HTMLPurifier($config);
+echo $purifier->purify('<b>Salsa!');
+--EXPECT--
+<b>Salsa!</b> \ No newline at end of file
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/error-auto-with-spl-nonstatic-autoload.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/error-auto-with-spl-nonstatic-autoload.phpt
new file mode 100644
index 000000000..9a91abaf8
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/error-auto-with-spl-nonstatic-autoload.phpt
@@ -0,0 +1,32 @@
+--TEST--
+Error when registering autoload with non-static autoload already on SPL stack
+--SKIPIF--
+<?php
+if (!function_exists('spl_autoload_register')) {
+ echo "skip - spl_autoload_register() not available";
+}
+if (version_compare(PHP_VERSION, '5.2.11', '>=')) {
+ echo "skip - non-buggy version of PHP";
+}
+--FILE--
+<?php
+class NotStatic
+{
+ public function autoload($class) {
+ echo "Autoloading... $class" . PHP_EOL;
+ eval("class $class {}");
+ }
+}
+
+$obj = new NotStatic();
+spl_autoload_register(array($obj, 'autoload'));
+
+try {
+ require '../library/HTMLPurifier.auto.php';
+} catch (Exception $e) {
+ echo 'Caught error gracefully';
+ assert('strpos($e->getMessage(), "44144") !== false');
+}
+
+--EXPECT--
+Caught error gracefully
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/path-includes-autoload.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/path-includes-autoload.phpt
new file mode 100644
index 000000000..6120956c6
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/path-includes-autoload.phpt
@@ -0,0 +1,14 @@
+--TEST--
+HTMLPurifier.path.php, HTMLPurifier.includes.php and HTMLPurifier.autoload.php loading test
+--FILE--
+<?php
+require '../library/HTMLPurifier.path.php';
+require 'HTMLPurifier.includes.php';
+require 'HTMLPurifier.autoload.php';
+require 'HTMLPurifier/PHPT/loading/_autoload.inc';
+$config = HTMLPurifier_Config::createDefault();
+$purifier = new HTMLPurifier($config);
+echo $purifier->purify('<b>Salsa!');
+
+--EXPECT--
+<b>Salsa!</b> \ No newline at end of file
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/path-includes.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/path-includes.phpt
new file mode 100644
index 000000000..681d51a96
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/path-includes.phpt
@@ -0,0 +1,12 @@
+--TEST--
+HTMLPurifier.path.php and HTMLPurifier.includes.php loading test
+--FILE--
+<?php
+require '../library/HTMLPurifier.path.php';
+require 'HTMLPurifier.includes.php';
+require 'HTMLPurifier/PHPT/loading/_no-autoload.inc';
+$config = HTMLPurifier_Config::createDefault();
+$purifier = new HTMLPurifier($config);
+echo $purifier->purify('<b>Salsa!');
+--EXPECT--
+<b>Salsa!</b> \ No newline at end of file
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/safe-includes.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/safe-includes.phpt
new file mode 100644
index 000000000..cb6f95d53
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/safe-includes.phpt
@@ -0,0 +1,12 @@
+--TEST--
+HTMLPurifier.safe-includes.php loading test
+--FILE--
+<?php
+require_once '../library/HTMLPurifier.php'; // Tests for require_once
+require_once '../library/HTMLPurifier.safe-includes.php';
+require 'HTMLPurifier/PHPT/loading/_no-autoload.inc';
+$config = HTMLPurifier_Config::createDefault();
+$purifier = new HTMLPurifier($config);
+echo $purifier->purify('<b>Salsa!');
+--EXPECT--
+<b>Salsa!</b> \ No newline at end of file
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone-autoload.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone-autoload.phpt
new file mode 100644
index 000000000..36bb2efd7
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone-autoload.phpt
@@ -0,0 +1,12 @@
+--TEST--
+HTMLPurifier.standalone.php loading test
+--FILE--
+<?php
+require '../library/HTMLPurifier.standalone.php';
+require '../library/HTMLPurifier.autoload.php';
+require 'HTMLPurifier/PHPT/loading/_autoload.inc';
+$config = HTMLPurifier_Config::createDefault();
+$purifier = new HTMLPurifier($config);
+echo $purifier->purify('<b>Salsa!');
+--EXPECT--
+<b>Salsa!</b> \ No newline at end of file
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone-with-prefix.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone-with-prefix.phpt
new file mode 100644
index 000000000..721dd7d17
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone-with-prefix.phpt
@@ -0,0 +1,15 @@
+--TEST--
+HTMLPurifier.standalone.php with HTMLPURIFIER_PREFIX loading test
+--FILE--
+<?php
+define('HTMLPURIFIER_PREFIX', realpath('../library'));
+require '../library/HTMLPurifier.path.php';
+require 'HTMLPurifier.standalone.php';
+require 'HTMLPurifier/Filter/YouTube.php';
+require 'HTMLPurifier/PHPT/loading/_no-autoload.inc';
+$config = HTMLPurifier_Config::createDefault();
+$purifier = new HTMLPurifier($config);
+echo $purifier->purify('<b>Salsa!');
+assert('in_array(realpath("../library/HTMLPurifier/Filter/YouTube.php"), get_included_files())');
+--EXPECT--
+<b>Salsa!</b> \ No newline at end of file
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone.phpt
new file mode 100644
index 000000000..a4fe4f77d
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone.phpt
@@ -0,0 +1,13 @@
+--TEST--
+HTMLPurifier.standalone.php loading test
+--FILE--
+<?php
+require '../library/HTMLPurifier.standalone.php';
+require 'HTMLPurifier/Filter/YouTube.php';
+require 'HTMLPurifier/PHPT/loading/_no-autoload.inc';
+$config = HTMLPurifier_Config::createDefault();
+$purifier = new HTMLPurifier($config);
+echo $purifier->purify('<b>Salsa!');
+assert('in_array(realpath("../library/standalone/HTMLPurifier/Filter/YouTube.php"), get_included_files())');
+--EXPECT--
+<b>Salsa!</b> \ No newline at end of file
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/stub.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/stub.phpt
new file mode 100644
index 000000000..e919c5763
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/stub.phpt
@@ -0,0 +1,6 @@
+--TEST--
+PHPT testing framework smoketest
+--FILE--
+Foobar
+--EXPECT--
+Foobar \ No newline at end of file
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/utf8.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/utf8.phpt
new file mode 100644
index 000000000..87c20ef66
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/utf8.phpt
@@ -0,0 +1,9 @@
+--TEST--
+UTF-8 smoketest
+--FILE--
+<?php
+require '../library/HTMLPurifier.auto.php';
+$purifier = new HTMLPurifier();
+echo $purifier->purify('太極拳, ЊЎЖ, لمنس');
+--EXPECT--
+太極拳, ЊЎЖ, لمنس \ No newline at end of file
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/ze1_compatibility_mode.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/ze1_compatibility_mode.phpt
new file mode 100644
index 000000000..606d7592e
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/ze1_compatibility_mode.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Error with zend.ze1_compatibility_mode test
+--PRESKIPIF--
+<?php
+if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
+ echo 'skip - ze1_compatibility_mode not present in PHP 5.3 or later';
+}
+--INI--
+zend.ze1_compatibility_mode = 1
+--FILE--
+<?php
+require '../library/HTMLPurifier.auto.php';
+--EXPECTF--
+Fatal error: HTML Purifier is not compatible with zend.ze1_compatibility_mode; please turn it off in %s
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PercentEncoderTest.php b/lib/htmlpurifier/tests/HTMLPurifier/PercentEncoderTest.php
new file mode 100644
index 000000000..fa969de13
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PercentEncoderTest.php
@@ -0,0 +1,63 @@
+<?php
+
+class HTMLPurifier_PercentEncoderTest extends HTMLPurifier_Harness
+{
+
+ protected $PercentEncoder;
+ protected $func;
+
+ function setUp() {
+ $this->PercentEncoder = new HTMLPurifier_PercentEncoder();
+ $this->func = '';
+ }
+
+ function assertDecode($string, $expect = true) {
+ if ($expect === true) $expect = $string;
+ $this->assertIdentical($this->PercentEncoder->{$this->func}($string), $expect);
+ }
+
+ function test_normalize() {
+ $this->func = 'normalize';
+
+ $this->assertDecode('Aw.../-$^8'); // no change
+ $this->assertDecode('%41%77%7E%2D%2E%5F', 'Aw~-._'); // decode unreserved chars
+ $this->assertDecode('%3A%2F%3F%23%5B%5D%40%21%24%26%27%28%29%2A%2B%2C%3B%3D'); // preserve reserved chars
+ $this->assertDecode('%2b', '%2B'); // normalize to uppercase
+ $this->assertDecode('%2B2B%3A3A'); // extra text
+ $this->assertDecode('%2b2B%4141', '%2B2BA41'); // extra text, with normalization
+ $this->assertDecode('%', '%25'); // normalize stray percent sign
+ $this->assertDecode('%5%25', '%255%25'); // permaturely terminated encoding
+ $this->assertDecode('%GJ', '%25GJ'); // invalid hexadecimal chars
+
+ // contested behavior, if this changes, we'll also have to have
+ // outbound encoding
+ $this->assertDecode('%FC'); // not reserved or unreserved, preserve
+
+ }
+
+ function assertEncode($string, $expect = true, $preserve = false) {
+ if ($expect === true) $expect = $string;
+ $encoder = new HTMLPurifier_PercentEncoder($preserve);
+ $result = $encoder->encode($string);
+ $this->assertIdentical($result, $expect);
+ }
+
+ function test_encode_noChange() {
+ $this->assertEncode('abc012-_~.');
+ }
+
+ function test_encode_encode() {
+ $this->assertEncode('>', '%3E');
+ }
+
+ function test_encode_preserve() {
+ $this->assertEncode('<>', '<%3E', '<');
+ }
+
+ function test_encode_low() {
+ $this->assertEncode("\1", '%01');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PropertyListTest.php b/lib/htmlpurifier/tests/HTMLPurifier/PropertyListTest.php
new file mode 100644
index 000000000..a2d4811ef
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/PropertyListTest.php
@@ -0,0 +1,93 @@
+<?php
+
+class HTMLPurifier_PropertyListTest extends UnitTestCase
+{
+
+ function testBasic() {
+ $plist = new HTMLPurifier_PropertyList();
+ $plist->set('key', 'value');
+ $this->assertIdentical($plist->get('key'), 'value');
+ }
+
+ function testNotFound() {
+ $this->expectException(new HTMLPurifier_Exception("Key 'key' not found"));
+ $plist = new HTMLPurifier_PropertyList();
+ $plist->get('key');
+ }
+
+ function testRecursion() {
+ $parent_plist = new HTMLPurifier_PropertyList();
+ $parent_plist->set('key', 'value');
+ $plist = new HTMLPurifier_PropertyList();
+ $plist->setParent($parent_plist);
+ $this->assertIdentical($plist->get('key'), 'value');
+ }
+
+ function testOverride() {
+ $parent_plist = new HTMLPurifier_PropertyList();
+ $parent_plist->set('key', 'value');
+ $plist = new HTMLPurifier_PropertyList();
+ $plist->setParent($parent_plist);
+ $plist->set('key', 'value2');
+ $this->assertIdentical($plist->get('key'), 'value2');
+ }
+
+ function testRecursionNotFound() {
+ $this->expectException(new HTMLPurifier_Exception("Key 'key' not found"));
+ $parent_plist = new HTMLPurifier_PropertyList();
+ $plist = new HTMLPurifier_PropertyList();
+ $plist->setParent($parent_plist);
+ $this->assertIdentical($plist->get('key'), 'value');
+ }
+
+ function testHas() {
+ $plist = new HTMLPurifier_PropertyList();
+ $this->assertIdentical($plist->has('key'), false);
+ $plist->set('key', 'value');
+ $this->assertIdentical($plist->has('key'), true);
+ }
+
+ function testReset() {
+ $plist = new HTMLPurifier_PropertyList();
+ $plist->set('key1', 'value');
+ $plist->set('key2', 'value');
+ $plist->set('key3', 'value');
+ $this->assertIdentical($plist->has('key1'), true);
+ $this->assertIdentical($plist->has('key2'), true);
+ $this->assertIdentical($plist->has('key3'), true);
+ $plist->reset('key2');
+ $this->assertIdentical($plist->has('key1'), true);
+ $this->assertIdentical($plist->has('key2'), false);
+ $this->assertIdentical($plist->has('key3'), true);
+ $plist->reset();
+ $this->assertIdentical($plist->has('key1'), false);
+ $this->assertIdentical($plist->has('key2'), false);
+ $this->assertIdentical($plist->has('key3'), false);
+ }
+
+ function testSquash() {
+ $parent = new HTMLPurifier_PropertyList();
+ $parent->set('key1', 'hidden');
+ $parent->set('key2', 2);
+ $plist = new HTMLPurifier_PropertyList($parent);
+ $plist->set('key1', 1);
+ $plist->set('key3', 3);
+ $this->assertIdentical(
+ $plist->squash(),
+ array('key1' => 1, 'key2' => 2, 'key3' => 3)
+ );
+ // updates don't show up...
+ $plist->set('key2', 22);
+ $this->assertIdentical(
+ $plist->squash(),
+ array('key1' => 1, 'key2' => 2, 'key3' => 3)
+ );
+ // until you force
+ $this->assertIdentical(
+ $plist->squash(true),
+ array('key1' => 1, 'key2' => 22, 'key3' => 3)
+ );
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/SimpleTest/Reporter.php b/lib/htmlpurifier/tests/HTMLPurifier/SimpleTest/Reporter.php
new file mode 100644
index 000000000..b9da9ab0b
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/SimpleTest/Reporter.php
@@ -0,0 +1,60 @@
+<?php
+
+class HTMLPurifier_SimpleTest_Reporter extends HTMLReporter
+{
+
+ protected $ac;
+
+ public function __construct($encoding, $ac) {
+ $this->ac = $ac;
+ parent::__construct($encoding);
+ }
+
+ public function paintHeader($test_name) {
+ parent::paintHeader($test_name);
+?>
+<form action="" method="get" id="select">
+ <select name="f">
+ <option value="" style="font-weight:bold;"<?php if(!$this->ac['file']) {echo ' selected';} ?>>All Tests</option>
+ <?php foreach($GLOBALS['HTMLPurifierTest']['Files'] as $file) { ?>
+ <option value="<?php echo $file ?>"<?php
+ if ($this->ac['file'] == $file) echo ' selected';
+ ?>><?php echo $file ?></option>
+ <?php } ?>
+ </select>
+ <input type="checkbox" name="standalone" value="1" title="Standalone version?" <?php if($this->ac['standalone']) {echo 'checked="checked" ';} ?>/>
+ <input type="submit" value="Go">
+</form>
+<?php
+ flush();
+ }
+
+ public function paintFooter($test_name) {
+ if (function_exists('xdebug_peak_memory_usage')) {
+ $max_mem = number_format(xdebug_peak_memory_usage());
+ echo "<div>Max memory usage: $max_mem bytes</div>";
+ }
+ parent::paintFooter($test_name);
+ }
+
+ protected function getCss() {
+ $css = parent::getCss();
+ $css .= '
+ #select {position:absolute;top:0.2em;right:0.2em;}
+ ';
+ return $css;
+ }
+
+ function getTestList() {
+ // hacky; depends on a specific implementation of paintPass, etc.
+ $list = parent::getTestList();
+ $testcase = $list[1];
+ if (class_exists($testcase, false)) $file = str_replace('_', '/', $testcase) . '.php';
+ else $file = $testcase;
+ $list[1] = '<a href="index.php?file=' . $file . '">' . $testcase . '</a>';
+ return $list;
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/SimpleTest/TextReporter.php b/lib/htmlpurifier/tests/HTMLPurifier/SimpleTest/TextReporter.php
new file mode 100644
index 000000000..56204e7a3
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/SimpleTest/TextReporter.php
@@ -0,0 +1,21 @@
+<?php
+
+class HTMLPurifier_SimpleTest_TextReporter extends TextReporter {
+ protected $verbose = false;
+ function __construct($AC) {
+ parent::__construct();
+ $this->verbose = $AC['verbose'];
+ }
+ function paintPass($message) {
+ parent::paintPass($message);
+ if ($this->verbose) {
+ print 'Pass ' . $this->getPassCount() . ") $message\n";
+ $breadcrumb = $this->getTestList();
+ array_shift($breadcrumb);
+ print "\tin " . implode("\n\tin ", array_reverse($breadcrumb));
+ print "\n";
+ }
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/CompositeTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/CompositeTest.php
new file mode 100644
index 000000000..8be422cc9
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/CompositeTest.php
@@ -0,0 +1,66 @@
+<?php
+
+class HTMLPurifier_Strategy_Composite_Test
+ extends HTMLPurifier_Strategy_Composite
+{
+
+ public function __construct(&$strategies) {
+ $this->strategies =& $strategies;
+ }
+
+}
+
+// doesn't use Strategy harness
+class HTMLPurifier_Strategy_CompositeTest extends HTMLPurifier_Harness
+{
+
+ function test() {
+
+ generate_mock_once('HTMLPurifier_Strategy');
+ generate_mock_once('HTMLPurifier_Config');
+ generate_mock_once('HTMLPurifier_Context');
+
+ // setup a bunch of mock strategies to inject into our composite test
+
+ $mock_1 = new HTMLPurifier_StrategyMock();
+ $mock_2 = new HTMLPurifier_StrategyMock();
+ $mock_3 = new HTMLPurifier_StrategyMock();
+
+ // setup the object
+
+ $strategies = array(&$mock_1, &$mock_2, &$mock_3);
+ $composite = new HTMLPurifier_Strategy_Composite_Test($strategies);
+
+ // setup expectations
+
+ $input_1 = 'This is raw data';
+ $input_2 = 'Processed by 1';
+ $input_3 = 'Processed by 1 and 2';
+ $input_4 = 'Processed by 1, 2 and 3'; // expected output
+
+ $config = new HTMLPurifier_ConfigMock();
+ $context = new HTMLPurifier_ContextMock();
+
+ $params_1 = array($input_1, $config, $context);
+ $params_2 = array($input_2, $config, $context);
+ $params_3 = array($input_3, $config, $context);
+
+ $mock_1->expectOnce('execute', $params_1);
+ $mock_1->setReturnValue('execute', $input_2, $params_1);
+
+ $mock_2->expectOnce('execute', $params_2);
+ $mock_2->setReturnValue('execute', $input_3, $params_2);
+
+ $mock_3->expectOnce('execute', $params_3);
+ $mock_3->setReturnValue('execute', $input_4, $params_3);
+
+ // perform test
+
+ $output = $composite->execute($input_1, $config, $context);
+ $this->assertIdentical($input_4, $output);
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/CoreTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/CoreTest.php
new file mode 100644
index 000000000..9bca5f60a
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/CoreTest.php
@@ -0,0 +1,45 @@
+<?php
+
+class HTMLPurifier_Strategy_CoreTest extends HTMLPurifier_StrategyHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_Strategy_Core();
+ }
+
+ function testBlankInput() {
+ $this->assertResult('');
+ }
+
+ function testMakeWellFormed() {
+ $this->assertResult(
+ '<b>Make well formed.',
+ '<b>Make well formed.</b>'
+ );
+ }
+
+ function testFixNesting() {
+ $this->assertResult(
+ '<b><div>Fix nesting.</div></b>',
+ '<b></b><div><b>Fix nesting.</b></div><b></b>'
+ );
+ }
+
+ function testRemoveForeignElements() {
+ $this->assertResult(
+ '<asdf>Foreign element removal.</asdf>',
+ 'Foreign element removal.'
+ );
+ }
+
+ function testFirstThree() {
+ $this->assertResult(
+ '<foo><b><div>All three.</div></b>',
+ '<b></b><div><b>All three.</b></div><b></b>'
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ErrorsHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ErrorsHarness.php
new file mode 100644
index 000000000..0e1bca681
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ErrorsHarness.php
@@ -0,0 +1,18 @@
+<?php
+
+class HTMLPurifier_Strategy_ErrorsHarness extends HTMLPurifier_ErrorsHarness
+{
+
+ // needs to be defined
+ protected function getStrategy() {}
+
+ protected function invoke($input) {
+ $strategy = $this->getStrategy();
+ $lexer = new HTMLPurifier_Lexer_DirectLex();
+ $tokens = $lexer->tokenizeHTML($input, $this->config, $this->context);
+ $strategy->execute($tokens, $this->config, $this->context);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/FixNestingTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/FixNestingTest.php
new file mode 100644
index 000000000..9394352ec
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/FixNestingTest.php
@@ -0,0 +1,144 @@
+<?php
+
+class HTMLPurifier_Strategy_FixNestingTest extends HTMLPurifier_StrategyHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_Strategy_FixNesting();
+ }
+
+ function testPreserveInlineInRoot() {
+ $this->assertResult('<b>Bold text</b>');
+ }
+
+ function testPreserveInlineAndBlockInRoot() {
+ $this->assertResult('<a href="about:blank">Blank</a><div>Block</div>');
+ }
+
+ function testRemoveBlockInInline() {
+ $this->assertResult(
+ '<b><div>Illegal div.</div></b>',
+ '<b>Illegal div.</b>'
+ );
+ }
+
+ function testEscapeBlockInInline() {
+ $this->config->set('Core.EscapeInvalidChildren', true);
+ $this->assertResult(
+ '<b><div>Illegal div.</div></b>',
+ '<b>&lt;div&gt;Illegal div.&lt;/div&gt;</b>'
+ );
+ }
+
+ function testRemoveNodeWithMissingRequiredElements() {
+ $this->assertResult('<ul></ul>', '');
+ }
+
+ function testListHandleIllegalPCDATA() {
+ $this->assertResult(
+ '<ul>Illegal text<li>Legal item</li></ul>',
+ '<ul><li>Illegal text</li><li>Legal item</li></ul>'
+ );
+ }
+
+ function testRemoveIllegalPCDATA() {
+ $this->assertResult(
+ '<table><tr>Illegal text<td></td></tr></table>',
+ '<table><tr><td></td></tr></table>'
+ );
+ }
+
+ function testCustomTableDefinition() {
+ $this->assertResult('<table><tr><td>Cell 1</td></tr></table>');
+ }
+
+ function testRemoveEmptyTable() {
+ $this->assertResult('<table></table>', '');
+ }
+
+ function testChameleonRemoveBlockInNodeInInline() {
+ $this->assertResult(
+ '<span><ins><div>Not allowed!</div></ins></span>',
+ '<span><ins>Not allowed!</ins></span>'
+ );
+ }
+
+ function testChameleonRemoveBlockInBlockNodeWithInlineContent() {
+ $this->assertResult(
+ '<h1><ins><div>Not allowed!</div></ins></h1>',
+ '<h1><ins>Not allowed!</ins></h1>'
+ );
+ }
+
+ function testNestedChameleonRemoveBlockInNodeWithInlineContent() {
+ $this->assertResult(
+ '<h1><ins><del><div>Not allowed!</div></del></ins></h1>',
+ '<h1><ins><del>Not allowed!</del></ins></h1>'
+ );
+ }
+
+ function testNestedChameleonPreserveBlockInBlock() {
+ $this->assertResult(
+ '<div><ins><del><div>Allowed!</div></del></ins></div>'
+ );
+ }
+
+ function testChameleonEscapeInvalidBlockInInline() {
+ $this->config->set('Core.EscapeInvalidChildren', true);
+ $this->assertResult( // alt config
+ '<span><ins><div>Not allowed!</div></ins></span>',
+ '<span><ins>&lt;div&gt;Not allowed!&lt;/div&gt;</ins></span>'
+ );
+ }
+
+ function testExclusionsIntegration() {
+ // test exclusions
+ $this->assertResult(
+ '<a><span><a>Not allowed</a></span></a>',
+ '<a><span></span></a>'
+ );
+ }
+
+ function testPreserveInlineNodeInInlineRootNode() {
+ $this->config->set('HTML.Parent', 'span');
+ $this->assertResult('<b>Bold</b>');
+ }
+
+ function testRemoveBlockNodeInInlineRootNode() {
+ $this->config->set('HTML.Parent', 'span');
+ $this->assertResult('<div>Reject</div>', 'Reject');
+ }
+
+ function testInvalidParentError() {
+ // test fallback to div
+ $this->config->set('HTML.Parent', 'obviously-impossible');
+ $this->config->set('Cache.DefinitionImpl', null);
+ $this->expectError('Cannot use unrecognized element as parent');
+ $this->assertResult('<div>Accept</div>');
+ }
+
+ function testCascadingRemovalOfNodesMissingRequiredChildren() {
+ $this->assertResult('<table><tr></tr></table>', '');
+ }
+
+ function testCascadingRemovalSpecialCaseCannotScrollOneBack() {
+ $this->assertResult('<table><tr></tr><tr></tr></table>', '');
+ }
+
+ function testLotsOfCascadingRemovalOfNodes() {
+ $this->assertResult('<table><tbody><tr></tr><tr></tr></tbody><tr></tr><tr></tr></table>', '');
+ }
+
+ function testAdjacentRemovalOfNodeMissingRequiredChildren() {
+ $this->assertResult('<table></table><table></table>', '');
+ }
+
+ function testStrictBlockquoteInHTML401() {
+ $this->config->set('HTML.Doctype', 'HTML 4.01 Strict');
+ $this->assertResult('<blockquote>text</blockquote>', '<blockquote><p>text</p></blockquote>');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/FixNesting_ErrorsTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/FixNesting_ErrorsTest.php
new file mode 100644
index 000000000..468480774
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/FixNesting_ErrorsTest.php
@@ -0,0 +1,41 @@
+<?php
+
+class HTMLPurifier_Strategy_FixNesting_ErrorsTest extends HTMLPurifier_Strategy_ErrorsHarness
+{
+
+ protected function getStrategy() {
+ return new HTMLPurifier_Strategy_FixNesting();
+ }
+
+ function testNodeRemoved() {
+ $this->expectErrorCollection(E_ERROR, 'Strategy_FixNesting: Node removed');
+ $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('ul', array(), 1));
+ $this->invoke('<ul></ul>');
+ }
+
+ function testNodeExcluded() {
+ $this->expectErrorCollection(E_ERROR, 'Strategy_FixNesting: Node excluded');
+ $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('a', array(), 2));
+ $this->invoke("<a>\n<a></a></a>");
+ }
+
+ function testNodeReorganized() {
+ $this->expectErrorCollection(E_WARNING, 'Strategy_FixNesting: Node reorganized');
+ $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('span', array(), 1));
+ $this->invoke("<span>Valid<div>Invalid</div></span>");
+ }
+
+ function testNoNodeReorganizedForEmptyNode() {
+ $this->expectNoErrorCollection();
+ $this->invoke("<span></span>");
+ }
+
+ function testNodeContentsRemoved() {
+ $this->expectErrorCollection(E_ERROR, 'Strategy_FixNesting: Node contents removed');
+ $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('span', array(), 1));
+ $this->invoke("<span><div></div></span>");
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndInsertInjector.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndInsertInjector.php
new file mode 100644
index 000000000..d1724bcb3
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndInsertInjector.php
@@ -0,0 +1,18 @@
+<?php
+
+class HTMLPurifier_Strategy_MakeWellFormed_EndInsertInjector extends HTMLPurifier_Injector
+{
+ public $name = 'EndInsertInjector';
+ public $needed = array('span');
+ public function handleEnd(&$token) {
+ if ($token->name == 'div') return;
+ $token = array(
+ new HTMLPurifier_Token_Start('b'),
+ new HTMLPurifier_Token_Text('Comment'),
+ new HTMLPurifier_Token_End('b'),
+ $token
+ );
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndInsertInjectorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndInsertInjectorTest.php
new file mode 100644
index 000000000..72e833b71
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndInsertInjectorTest.php
@@ -0,0 +1,38 @@
+<?php
+
+class HTMLPurifier_Strategy_MakeWellFormed_EndInsertInjectorTest extends HTMLPurifier_StrategyHarness
+{
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_Strategy_MakeWellFormed();
+ $this->config->set('AutoFormat.Custom', array(
+ new HTMLPurifier_Strategy_MakeWellFormed_EndInsertInjector()
+ ));
+ }
+ function testEmpty() {
+ $this->assertResult('');
+ }
+ function testNormal() {
+ $this->assertResult('<i>Foo</i>', '<i>Foo<b>Comment</b></i>');
+ }
+ function testEndOfDocumentProcessing() {
+ $this->assertResult('<i>Foo', '<i>Foo<b>Comment</b></i>');
+ }
+ function testDoubleEndOfDocumentProcessing() {
+ $this->assertResult('<i><i>Foo', '<i><i>Foo<b>Comment</b></i><b>Comment</b></i>');
+ }
+ function testEndOfNodeProcessing() {
+ $this->assertResult('<div><i>Foo</div>asdf', '<div><i>Foo<b>Comment</b></i></div><i>asdf<b>Comment</b></i>');
+ }
+ function testEmptyToStartEndProcessing() {
+ $this->assertResult('<i />', '<i><b>Comment</b></i>');
+ }
+ function testSpuriousEndTag() {
+ $this->assertResult('</i>', '');
+ }
+ function testLessButStillSpuriousEndTag() {
+ $this->assertResult('<div></i></div>', '<div></div>');
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndRewindInjector.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndRewindInjector.php
new file mode 100644
index 000000000..21c491436
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndRewindInjector.php
@@ -0,0 +1,29 @@
+<?php
+
+class HTMLPurifier_Strategy_MakeWellFormed_EndRewindInjector extends HTMLPurifier_Injector
+{
+ public $name = 'EndRewindInjector';
+ public $needed = array('span');
+ public function handleElement(&$token) {
+ if (isset($token->_InjectorTest_EndRewindInjector_delete)) {
+ $token = false;
+ }
+ }
+ public function handleText(&$token) {
+ $token = false;
+ }
+ public function handleEnd(&$token) {
+ $i = null;
+ if (
+ $this->backward($i, $prev) &&
+ $prev instanceof HTMLPurifier_Token_Start &&
+ $prev->name == 'span'
+ ) {
+ $token = false;
+ $prev->_InjectorTest_EndRewindInjector_delete = true;
+ $this->rewind($i);
+ }
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndRewindInjectorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndRewindInjectorTest.php
new file mode 100644
index 000000000..96c88748d
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndRewindInjectorTest.php
@@ -0,0 +1,33 @@
+<?php
+
+class HTMLPurifier_Strategy_MakeWellFormed_EndRewindInjectorTest extends HTMLPurifier_StrategyHarness
+{
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_Strategy_MakeWellFormed();
+ $this->config->set('AutoFormat.Custom', array(
+ new HTMLPurifier_Strategy_MakeWellFormed_EndRewindInjector()
+ ));
+ }
+ function testBasic() {
+ $this->assertResult('');
+ }
+ function testFunction() {
+ $this->assertResult('<span>asdf</span>','');
+ }
+ function testFailedFunction() {
+ $this->assertResult('<span>asd<b>asdf</b>asdf</span>','<span><b></b></span>');
+ }
+ function testPadded() {
+ $this->assertResult('<b></b><span>asdf</span><b></b>','<b></b><b></b>');
+ }
+ function testDoubled() {
+ $this->config->set('AutoFormat.Custom', array(
+ new HTMLPurifier_Strategy_MakeWellFormed_EndRewindInjector(),
+ new HTMLPurifier_Strategy_MakeWellFormed_EndRewindInjector(),
+ ));
+ $this->assertResult('<b></b><span>asdf</span>', '<b></b>');
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/SkipInjector.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/SkipInjector.php
new file mode 100644
index 000000000..258346b8d
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/SkipInjector.php
@@ -0,0 +1,12 @@
+<?php
+
+class HTMLPurifier_Strategy_MakeWellFormed_SkipInjector extends HTMLPurifier_Injector
+{
+ public $name = 'EndRewindInjector';
+ public $needed = array('span');
+ public function handleElement(&$token) {
+ $token = array(clone $token, clone $token);
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/SkipInjectorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/SkipInjectorTest.php
new file mode 100644
index 000000000..770b3d84e
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/SkipInjectorTest.php
@@ -0,0 +1,27 @@
+<?php
+
+class HTMLPurifier_Strategy_MakeWellFormed_SkipInjectorTest extends HTMLPurifier_StrategyHarness
+{
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_Strategy_MakeWellFormed();
+ $this->config->set('AutoFormat.Custom', array(
+ new HTMLPurifier_Strategy_MakeWellFormed_SkipInjector()
+ ));
+ }
+ function testEmpty() {
+ $this->assertResult('');
+ }
+ function testMultiply() {
+ $this->assertResult('<br />', '<br /><br />');
+ }
+ function testMultiplyMultiply() {
+ $this->config->set('AutoFormat.Custom', array(
+ new HTMLPurifier_Strategy_MakeWellFormed_SkipInjector(),
+ new HTMLPurifier_Strategy_MakeWellFormed_SkipInjector()
+ ));
+ $this->assertResult('<br />', '<br /><br /><br /><br />');
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormedTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormedTest.php
new file mode 100644
index 000000000..c6d775c5a
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormedTest.php
@@ -0,0 +1,149 @@
+<?php
+
+class HTMLPurifier_Strategy_MakeWellFormedTest extends HTMLPurifier_StrategyHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_Strategy_MakeWellFormed();
+ }
+
+ function testEmptyInput() {
+ $this->assertResult('');
+ }
+
+ function testWellFormedInput() {
+ $this->assertResult('This is <b>bold text</b>.');
+ }
+
+ function testUnclosedTagTerminatedByDocumentEnd() {
+ $this->assertResult(
+ '<b>Unclosed tag, gasp!',
+ '<b>Unclosed tag, gasp!</b>'
+ );
+ }
+
+ function testUnclosedTagTerminatedByParentNodeEnd() {
+ $this->assertResult(
+ '<b><i>Bold and italic?</b>',
+ '<b><i>Bold and italic?</i></b><i></i>'
+ );
+ }
+
+ function testRemoveStrayClosingTag() {
+ $this->assertResult(
+ 'Unused end tags... recycle!</b>',
+ 'Unused end tags... recycle!'
+ );
+ }
+
+ function testConvertStartToEmpty() {
+ $this->assertResult(
+ '<br style="clear:both;">',
+ '<br style="clear:both;" />'
+ );
+ }
+
+ function testConvertEmptyToStart() {
+ $this->assertResult(
+ '<div style="clear:both;" />',
+ '<div style="clear:both;"></div>'
+ );
+ }
+
+ function testAutoCloseParagraph() {
+ $this->assertResult(
+ '<p>Paragraph 1<p>Paragraph 2',
+ '<p>Paragraph 1</p><p>Paragraph 2</p>'
+ );
+ }
+
+ function testAutoCloseParagraphInsideDiv() {
+ $this->assertResult(
+ '<div><p>Paragraphs<p>In<p>A<p>Div</div>',
+ '<div><p>Paragraphs</p><p>In</p><p>A</p><p>Div</p></div>'
+ );
+ }
+
+ function testAutoCloseListItem() {
+ $this->assertResult(
+ '<ol><li>Item 1<li>Item 2</ol>',
+ '<ol><li>Item 1</li><li>Item 2</li></ol>'
+ );
+ }
+
+ function testAutoCloseColgroup() {
+ $this->assertResult(
+ '<table><colgroup><col /><tr></tr></table>',
+ '<table><colgroup><col /></colgroup><tr></tr></table>'
+ );
+ }
+
+ function testAutoCloseMultiple() {
+ $this->assertResult(
+ '<b><span><div></div>asdf',
+ '<b><span></span></b><div><b></b></div><b>asdf</b>'
+ );
+ }
+
+ function testUnrecognized() {
+ $this->assertResult(
+ '<asdf><foobar /><biddles>foo</asdf>',
+ '<asdf><foobar /><biddles>foo</biddles></asdf>'
+ );
+ }
+
+ function testBlockquoteWithInline() {
+ $this->config->set('HTML.Doctype', 'XHTML 1.0 Strict');
+ $this->assertResult(
+ // This is actually invalid, but will be fixed by
+ // ChildDef_StrictBlockquote
+ '<blockquote>foo<b>bar</b></blockquote>'
+ );
+ }
+
+ function testLongCarryOver() {
+ $this->assertResult(
+ '<b>asdf<div>asdf<i>df</i></div>asdf</b>',
+ '<b>asdf</b><div><b>asdf<i>df</i></b></div><b>asdf</b>'
+ );
+ }
+
+ function testInterleaved() {
+ $this->assertResult(
+ '<u>foo<i>bar</u>baz</i>',
+ '<u>foo<i>bar</i></u><i>baz</i>'
+ );
+ }
+
+ function testNestedOl() {
+ $this->assertResult(
+ '<ol><ol><li>foo</li></ol></ol>',
+ '<ol><ol><li>foo</li></ol></ol>'
+ );
+ }
+
+ function testNestedUl() {
+ $this->assertResult(
+ '<ul><ul><li>foo</li></ul></ul>',
+ '<ul><ul><li>foo</li></ul></ul>'
+ );
+ }
+
+ function testNestedOlWithStrangeEnding() {
+ $this->assertResult(
+ '<ol><li><ol><ol><li>foo</li></ol></li><li>foo</li></ol>',
+ '<ol><li><ol><ol><li>foo</li></ol></ol></li><li>foo</li></ol>'
+ );
+ }
+
+ function testNoAutocloseIfNoParentsCanAccomodateTag() {
+ $this->assertResult(
+ '<table><tr><td><li>foo</li></td></tr></table>',
+ '<table><tr><td>foo</td></tr></table>'
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed_ErrorsTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed_ErrorsTest.php
new file mode 100644
index 000000000..e825e2e2a
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed_ErrorsTest.php
@@ -0,0 +1,62 @@
+<?php
+
+class HTMLPurifier_Strategy_MakeWellFormed_ErrorsTest extends HTMLPurifier_Strategy_ErrorsHarness
+{
+
+ protected function getStrategy() {
+ return new HTMLPurifier_Strategy_MakeWellFormed();
+ }
+
+ function testUnnecessaryEndTagRemoved() {
+ $this->expectErrorCollection(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed');
+ $this->expectContext('CurrentToken', new HTMLPurifier_Token_End('b', array(), 1, 0));
+ $this->invoke('</b>');
+ }
+
+ function testUnnecessaryEndTagToText() {
+ $this->config->set('Core.EscapeInvalidTags', true);
+ $this->expectErrorCollection(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text');
+ $this->expectContext('CurrentToken', new HTMLPurifier_Token_End('b', array(), 1, 0));
+ $this->invoke('</b>');
+ }
+
+ function testTagAutoclose() {
+ $this->expectErrorCollection(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', new HTMLPurifier_Token_Start('p', array(), 1, 0));
+ $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('div', array(), 1, 6));
+ $this->invoke('<p>Foo<div>Bar</div>');
+ }
+
+ function testTagCarryOver() {
+ $b = new HTMLPurifier_Token_Start('b', array(), 1, 0);
+ $this->expectErrorCollection(E_NOTICE, 'Strategy_MakeWellFormed: Tag carryover', $b);
+ $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('div', array(), 1, 6));
+ $this->invoke('<b>Foo<div>Bar</div>');
+ }
+
+ function testStrayEndTagRemoved() {
+ $this->expectErrorCollection(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed');
+ $this->expectContext('CurrentToken', new HTMLPurifier_Token_End('b', array(), 1, 3));
+ $this->invoke('<i></b></i>');
+ }
+
+ function testStrayEndTagToText() {
+ $this->config->set('Core.EscapeInvalidTags', true);
+ $this->expectErrorCollection(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text');
+ $this->expectContext('CurrentToken', new HTMLPurifier_Token_End('b', array(), 1, 3));
+ $this->invoke('<i></b></i>');
+ }
+
+ function testTagClosedByElementEnd() {
+ $this->expectErrorCollection(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', new HTMLPurifier_Token_Start('b', array(), 1, 3));
+ $this->expectContext('CurrentToken', new HTMLPurifier_Token_End('i', array(), 1, 12));
+ $this->invoke('<i><b>Foobar</i>');
+ }
+
+ function testTagClosedByDocumentEnd() {
+ $this->expectErrorCollection(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', new HTMLPurifier_Token_Start('b', array(), 1, 0));
+ $this->invoke('<b>Foobar');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed_InjectorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed_InjectorTest.php
new file mode 100644
index 000000000..f0e01340a
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed_InjectorTest.php
@@ -0,0 +1,149 @@
+<?php
+
+class HTMLPurifier_Strategy_MakeWellFormed_InjectorTest extends HTMLPurifier_StrategyHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_Strategy_MakeWellFormed();
+ $this->config->set('AutoFormat.AutoParagraph', true);
+ $this->config->set('AutoFormat.Linkify', true);
+ $this->config->set('AutoFormat.RemoveEmpty', true);
+ generate_mock_once('HTMLPurifier_Injector');
+ }
+
+ function testEndHandler() {
+ $mock = new HTMLPurifier_InjectorMock();
+ $b = new HTMLPurifier_Token_End('b');
+ $b->skip = array(0 => true);
+ $b->start = new HTMLPurifier_Token_Start('b');
+ $b->start->skip = array(0 => true, 1 => true);
+ $mock->expectAt(0, 'handleEnd', array($b));
+ $i = new HTMLPurifier_Token_End('i');
+ $i->start = new HTMLPurifier_Token_Start('i');
+ $i->skip = array(0 => true);
+ $i->start->skip = array(0 => true, 1 => true);
+ $mock->expectAt(1, 'handleEnd', array($i));
+ $mock->expectCallCount('handleEnd', 2);
+ $mock->setReturnValue('getRewind', false);
+ $this->config->set('AutoFormat.AutoParagraph', false);
+ $this->config->set('AutoFormat.Linkify', false);
+ $this->config->set('AutoFormat.Custom', array($mock));
+ $this->assertResult('<i><b>asdf</b>', '<i><b>asdf</b></i>');
+ }
+
+ function testErrorRequiredElementNotAllowed() {
+ $this->config->set('HTML.Allowed', '');
+ $this->expectError('Cannot enable AutoParagraph injector because p is not allowed');
+ $this->expectError('Cannot enable Linkify injector because a is not allowed');
+ $this->assertResult('Foobar');
+ }
+
+ function testErrorRequiredAttributeNotAllowed() {
+ $this->config->set('HTML.Allowed', 'a,p');
+ $this->expectError('Cannot enable Linkify injector because a.href is not allowed');
+ $this->assertResult('<p>http://example.com</p>');
+ }
+
+ function testOnlyAutoParagraph() {
+ $this->assertResult(
+ 'Foobar',
+ '<p>Foobar</p>'
+ );
+ }
+
+ function testParagraphWrappingOnlyLink() {
+ $this->assertResult(
+ 'http://example.com',
+ '<p><a href="http://example.com">http://example.com</a></p>'
+ );
+ }
+
+ function testParagraphWrappingNodeContainingLink() {
+ $this->assertResult(
+ '<b>http://example.com</b>',
+ '<p><b><a href="http://example.com">http://example.com</a></b></p>'
+ );
+ }
+
+ function testParagraphWrappingPoorlyFormedNodeContainingLink() {
+ $this->assertResult(
+ '<b>http://example.com',
+ '<p><b><a href="http://example.com">http://example.com</a></b></p>'
+ );
+ }
+
+ function testTwoParagraphsContainingOnlyOneLink() {
+ $this->assertResult(
+ "http://example.com\n\nhttp://dev.example.com",
+'<p><a href="http://example.com">http://example.com</a></p>
+
+<p><a href="http://dev.example.com">http://dev.example.com</a></p>'
+ );
+ }
+
+ function testParagraphNextToDivWithLinks() {
+ $this->assertResult(
+ 'http://example.com <div>http://example.com</div>',
+'<p><a href="http://example.com">http://example.com</a> </p>
+
+<div><a href="http://example.com">http://example.com</a></div>'
+ );
+ }
+
+ function testRealisticLinkInSentence() {
+ $this->assertResult(
+ 'This URL http://example.com is what you need',
+ '<p>This URL <a href="http://example.com">http://example.com</a> is what you need</p>'
+ );
+ }
+
+ function testParagraphAfterLinkifiedURL() {
+ $this->assertResult(
+"http://google.com
+
+<b>b</b>",
+"<p><a href=\"http://google.com\">http://google.com</a></p>
+
+<p><b>b</b></p>"
+ );
+ }
+
+ function testEmptyAndParagraph() {
+ // This is a fairly degenerate case, but it demonstrates that
+ // the two don't error out together, at least.
+ // Change this behavior!
+ $this->assertResult(
+"<p>asdf
+
+asdf<b></b></p>
+
+<p></p><i></i>",
+"<p>asdf</p>
+
+<p>asdf</p>
+
+"
+ );
+ }
+
+ function testRewindAndParagraph() {
+ $this->assertResult(
+"bar
+
+<p><i></i>
+
+</p>
+
+foo",
+"<p>bar</p>
+
+
+
+<p>foo</p>"
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElementsTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElementsTest.php
new file mode 100644
index 000000000..b3ca1646a
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElementsTest.php
@@ -0,0 +1,115 @@
+<?php
+
+class HTMLPurifier_Strategy_RemoveForeignElementsTest extends HTMLPurifier_StrategyHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_Strategy_RemoveForeignElements();
+ }
+
+ function testBlankInput() {
+ $this->assertResult('');
+ }
+
+ function testPreserveRecognizedElements() {
+ $this->assertResult('This is <b>bold text</b>.');
+ }
+
+ function testRemoveForeignElements() {
+ $this->assertResult(
+ '<asdf>Bling</asdf><d href="bang">Bong</d><foobar />',
+ 'BlingBong'
+ );
+ }
+
+ function testRemoveScriptAndContents() {
+ $this->assertResult(
+ '<script>alert();</script>',
+ ''
+ );
+ }
+
+ function testRemoveStyleAndContents() {
+ $this->assertResult(
+ '<style>.foo {blink;}</style>',
+ ''
+ );
+ }
+
+ function testRemoveOnlyScriptTagsLegacy() {
+ $this->config->set('Core.RemoveScriptContents', false);
+ $this->assertResult(
+ '<script>alert();</script>',
+ 'alert();'
+ );
+ }
+
+ function testRemoveOnlyScriptTags() {
+ $this->config->set('Core.HiddenElements', array());
+ $this->assertResult(
+ '<script>alert();</script>',
+ 'alert();'
+ );
+ }
+
+ function testRemoveInvalidImg() {
+ $this->assertResult('<img />', '');
+ }
+
+ function testPreserveValidImg() {
+ $this->assertResult('<img src="foobar.gif" alt="foobar.gif" />');
+ }
+
+ function testPreserveInvalidImgWhenRemovalIsDisabled() {
+ $this->config->set('Core.RemoveInvalidImg', false);
+ $this->assertResult('<img />');
+ }
+
+ function testTextifyCommentedScriptContents() {
+ $this->config->set('HTML.Trusted', true);
+ $this->config->set('Output.CommentScriptContents', false); // simplify output
+ $this->assertResult(
+'<script type="text/javascript"><!--
+alert(<b>bold</b>);
+// --></script>',
+'<script type="text/javascript">
+alert(&lt;b&gt;bold&lt;/b&gt;);
+// </script>'
+ );
+ }
+
+ function testRequiredAttributesTestNotPerformedOnEndTag() {
+ $def = $this->config->getHTMLDefinition(true);
+ $def->addElement('f', 'Block', 'Optional: #PCDATA', false, array('req*' => 'Text'));
+ $this->assertResult('<f req="text">Foo</f> Bar');
+ }
+
+ function testPreserveCommentsWithHTMLTrusted() {
+ $this->config->set('HTML.Trusted', true);
+ $this->assertResult('<!-- foo -->');
+ }
+
+ function testRemoveTrailingHyphensInComment() {
+ $this->config->set('HTML.Trusted', true);
+ $this->assertResult('<!-- foo ----->', '<!-- foo -->');
+ }
+
+ function testCollapseDoubleHyphensInComment() {
+ $this->config->set('HTML.Trusted', true);
+ $this->assertResult('<!-- bo --- asdf--as -->', '<!-- bo - asdf-as -->');
+ }
+
+ function testPreserveCommentsWithLookup() {
+ $this->config->set('HTML.AllowedComments', array('allowed'));
+ $this->assertResult('<!-- allowed --><!-- not allowed -->', '<!-- allowed -->');
+ }
+
+ function testPreserveCommentsWithRegexp() {
+ $this->config->set('HTML.AllowedCommentsRegexp', '/^allowed[1-9]$/');
+ $this->assertResult('<!-- allowed1 --><!-- not allowed -->', '<!-- allowed1 -->');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElements_ErrorsTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElements_ErrorsTest.php
new file mode 100644
index 000000000..4b4e31074
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElements_ErrorsTest.php
@@ -0,0 +1,71 @@
+<?php
+
+class HTMLPurifier_Strategy_RemoveForeignElements_ErrorsTest extends HTMLPurifier_Strategy_ErrorsHarness
+{
+
+ public function setup() {
+ parent::setup();
+ $this->config->set('HTML.TidyLevel', 'heavy');
+ }
+
+ protected function getStrategy() {
+ return new HTMLPurifier_Strategy_RemoveForeignElements();
+ }
+
+ function testTagTransform() {
+ $this->expectErrorCollection(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', 'center');
+ $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('div', array('style' => 'text-align:center;'), 1));
+ $this->invoke('<center>');
+ }
+
+ function testMissingRequiredAttr() {
+ // a little fragile, since img has two required attributes
+ $this->expectErrorCollection(E_ERROR, 'Strategy_RemoveForeignElements: Missing required attribute', 'alt');
+ $this->expectContext('CurrentToken', new HTMLPurifier_Token_Empty('img', array(), 1));
+ $this->invoke('<img />');
+ }
+
+ function testForeignElementToText() {
+ $this->config->set('Core.EscapeInvalidTags', true);
+ $this->expectErrorCollection(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text');
+ $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('invalid', array(), 1));
+ $this->invoke('<invalid>');
+ }
+
+ function testForeignElementRemoved() {
+ // uses $CurrentToken.Serialized
+ $this->expectErrorCollection(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed');
+ $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('invalid', array(), 1));
+ $this->invoke('<invalid>');
+ }
+
+ function testCommentRemoved() {
+ $this->expectErrorCollection(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');
+ $this->expectContext('CurrentToken', new HTMLPurifier_Token_Comment(' test ', 1));
+ $this->invoke('<!-- test -->');
+ }
+
+ function testTrailingHyphenInCommentRemoved() {
+ $this->config->set('HTML.Trusted', true);
+ $this->expectErrorCollection(E_NOTICE, 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed');
+ $this->expectContext('CurrentToken', new HTMLPurifier_Token_Comment(' test ', 1));
+ $this->invoke('<!-- test ---->');
+ }
+
+ function testDoubleHyphenInCommentRemoved() {
+ $this->config->set('HTML.Trusted', true);
+ $this->expectErrorCollection(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed');
+ $this->expectContext('CurrentToken', new HTMLPurifier_Token_Comment(' test - test - test ', 1));
+ $this->invoke('<!-- test --- test -- test -->');
+ }
+
+ function testForeignMetaElementRemoved() {
+ $this->collector->expectAt(0, 'send', array(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed'));
+ $this->collector->expectContextAt(0, 'CurrentToken', new HTMLPurifier_Token_Start('script', array(), 1));
+ $this->collector->expectAt(1, 'send', array(E_ERROR, 'Strategy_RemoveForeignElements: Token removed to end', 'script'));
+ $this->invoke('<script>asdf');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElements_TidyTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElements_TidyTest.php
new file mode 100644
index 000000000..22fc456a7
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElements_TidyTest.php
@@ -0,0 +1,45 @@
+<?php
+
+class HTMLPurifier_Strategy_RemoveForeignElements_TidyTest
+ extends HTMLPurifier_StrategyHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_Strategy_RemoveForeignElements();
+ $this->config->set('HTML.TidyLevel', 'heavy');
+ }
+
+ function testCenterTransform() {
+ $this->assertResult(
+ '<center>Look I am Centered!</center>',
+ '<div style="text-align:center;">Look I am Centered!</div>'
+ );
+ }
+
+ function testFontTransform() {
+ $this->assertResult(
+ '<font color="red" face="Arial" size="6">Big Warning!</font>',
+ '<span style="color:red;font-family:Arial;font-size:xx-large;">Big'.
+ ' Warning!</span>'
+ );
+ }
+
+ function testTransformToForbiddenElement() {
+ $this->config->set('HTML.Allowed', 'div');
+ $this->assertResult(
+ '<font color="red" face="Arial" size="6">Big Warning!</font>',
+ 'Big Warning!'
+ );
+ }
+
+ function testMenuTransform() {
+ $this->assertResult(
+ '<menu><li>Item 1</li></menu>',
+ '<ul><li>Item 1</li></ul>'
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributesTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributesTest.php
new file mode 100644
index 000000000..5fc86cbda
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributesTest.php
@@ -0,0 +1,232 @@
+<?php
+
+class HTMLPurifier_Strategy_ValidateAttributesTest extends
+ HTMLPurifier_StrategyHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_Strategy_ValidateAttributes();
+ }
+
+ function testEmptyInput() {
+ $this->assertResult('');
+ }
+
+ function testRemoveIDByDefault() {
+ $this->assertResult(
+ '<div id="valid">Kill the ID.</div>',
+ '<div>Kill the ID.</div>'
+ );
+ }
+
+ function testRemoveInvalidDir() {
+ $this->assertResult(
+ '<span dir="up-to-down">Bad dir.</span>',
+ '<span>Bad dir.</span>'
+ );
+ }
+
+ function testPreserveValidClass() {
+ $this->assertResult('<div class="valid">Valid</div>');
+ }
+
+ function testSelectivelyRemoveInvalidClasses() {
+ $this->config->set('HTML.Doctype', 'XHTML 1.1');
+ $this->assertResult(
+ '<div class="valid 0invalid">Keep valid.</div>',
+ '<div class="valid">Keep valid.</div>'
+ );
+ }
+
+ function testPreserveTitle() {
+ $this->assertResult(
+ '<acronym title="PHP: Hypertext Preprocessor">PHP</acronym>'
+ );
+ }
+
+ function testAddXMLLang() {
+ $this->assertResult(
+ '<span lang="fr">La soupe.</span>',
+ '<span lang="fr" xml:lang="fr">La soupe.</span>'
+ );
+ }
+
+ function testOnlyXMLLangInXHTML11() {
+ $this->config->set('HTML.Doctype', 'XHTML 1.1');
+ $this->assertResult(
+ '<b lang="en">asdf</b>',
+ '<b xml:lang="en">asdf</b>'
+ );
+ }
+
+ function testBasicURI() {
+ $this->assertResult('<a href="http://www.google.com/">Google</a>');
+ }
+
+ function testInvalidURI() {
+ $this->assertResult(
+ '<a href="javascript:badstuff();">Google</a>',
+ '<a>Google</a>'
+ );
+ }
+
+ function testBdoAddMissingDir() {
+ $this->assertResult(
+ '<bdo>Go left.</bdo>',
+ '<bdo dir="ltr">Go left.</bdo>'
+ );
+ }
+
+ function testBdoReplaceInvalidDirWithDefault() {
+ $this->assertResult(
+ '<bdo dir="blahblah">Invalid value!</bdo>',
+ '<bdo dir="ltr">Invalid value!</bdo>'
+ );
+ }
+
+ function testBdoAlternateDefaultDir() {
+ $this->config->set('Attr.DefaultTextDir', 'rtl');
+ $this->assertResult(
+ '<bdo>Go right.</bdo>',
+ '<bdo dir="rtl">Go right.</bdo>'
+ );
+ }
+
+ function testRemoveDirWhenNotRequired() {
+ $this->assertResult(
+ '<span dir="blahblah">Invalid value!</span>',
+ '<span>Invalid value!</span>'
+ );
+ }
+
+ function testTableAttributes() {
+ $this->assertResult(
+'<table frame="above" rules="rows" summary="A test table" border="2" cellpadding="5%" cellspacing="3" width="100%">
+ <col align="right" width="4*" />
+ <col charoff="5" align="char" width="*" />
+ <tr valign="top">
+ <th abbr="name">Fiddly name</th>
+ <th abbr="price">Super-duper-price</th>
+ </tr>
+ <tr>
+ <td abbr="carrot">Carrot Humungous</td>
+ <td>$500.23</td>
+ </tr>
+ <tr>
+ <td colspan="2">Taken off the market</td>
+ </tr>
+</table>'
+ );
+ }
+
+ function testColSpanIsNonZero() {
+ $this->assertResult(
+ '<col span="0" />',
+ '<col />'
+ );
+ }
+
+ function testImgAddDefaults() {
+ $this->config->set('Core.RemoveInvalidImg', false);
+ $this->assertResult(
+ '<img />',
+ '<img src="" alt="Invalid image" />'
+ );
+ }
+
+ function testImgGenerateAlt() {
+ $this->assertResult(
+ '<img src="foobar.jpg" />',
+ '<img src="foobar.jpg" alt="foobar.jpg" />'
+ );
+ }
+
+ function testImgAddDefaultSrc() {
+ $this->config->set('Core.RemoveInvalidImg', false);
+ $this->assertResult(
+ '<img alt="pretty picture" />',
+ '<img alt="pretty picture" src="" />'
+ );
+ }
+
+ function testImgRemoveNonRetrievableProtocol() {
+ $this->config->set('Core.RemoveInvalidImg', false);
+ $this->assertResult(
+ '<img src="mailto:foo@example.com" />',
+ '<img alt="mailto:foo@example.com" src="" />'
+ );
+ }
+
+ function testPreserveRel() {
+ $this->config->set('Attr.AllowedRel', 'nofollow');
+ $this->assertResult('<a href="foo" rel="nofollow" />');
+ }
+
+ function testPreserveTarget() {
+ $this->config->set('Attr.AllowedFrameTargets', '_top');
+ $this->config->set('HTML.Doctype', 'XHTML 1.0 Transitional');
+ $this->assertResult('<a href="foo" target="_top" />');
+ }
+
+ function testRemoveTargetWhenNotSupported() {
+ $this->config->set('HTML.Doctype', 'XHTML 1.0 Strict');
+ $this->config->set('Attr.AllowedFrameTargets', '_top');
+ $this->assertResult(
+ '<a href="foo" target="_top" />',
+ '<a href="foo" />'
+ );
+ }
+
+ function testKeepAbsoluteCSSWidthAndHeightOnImg() {
+ $this->assertResult(
+ '<img src="" alt="" style="width:10px;height:10px;border:1px solid #000;" />'
+ );
+ }
+
+ function testRemoveLargeCSSWidthAndHeightOnImg() {
+ $this->assertResult(
+ '<img src="" alt="" style="width:10000000px;height:10000000px;border:1px solid #000;" />',
+ '<img src="" alt="" style="border:1px solid #000;" />'
+ );
+ }
+
+ function testRemoveLargeCSSWidthAndHeightOnImgWithUserConf() {
+ $this->config->set('CSS.MaxImgLength', '1px');
+ $this->assertResult(
+ '<img src="" alt="" style="width:1mm;height:1mm;border:1px solid #000;" />',
+ '<img src="" alt="" style="border:1px solid #000;" />'
+ );
+ }
+
+ function testKeepLargeCSSWidthAndHeightOnImgWhenToldTo() {
+ $this->config->set('CSS.MaxImgLength', null);
+ $this->assertResult(
+ '<img src="" alt="" style="width:10000000px;height:10000000px;border:1px solid #000;" />'
+ );
+ }
+
+ function testKeepPercentCSSWidthAndHeightOnImgWhenToldTo() {
+ $this->config->set('CSS.MaxImgLength', null);
+ $this->assertResult(
+ '<img src="" alt="" style="width:100%;height:100%;border:1px solid #000;" />'
+ );
+ }
+
+ function testRemoveRelativeCSSWidthAndHeightOnImg() {
+ $this->assertResult(
+ '<img src="" alt="" style="width:10em;height:10em;border:1px solid #000;" />',
+ '<img src="" alt="" style="border:1px solid #000;" />'
+ );
+ }
+
+ function testRemovePercentCSSWidthAndHeightOnImg() {
+ $this->assertResult(
+ '<img src="" alt="" style="width:100%;height:100%;border:1px solid #000;" />',
+ '<img src="" alt="" style="border:1px solid #000;" />'
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributes_IDTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributes_IDTest.php
new file mode 100644
index 000000000..c8c750723
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributes_IDTest.php
@@ -0,0 +1,63 @@
+<?php
+
+class HTMLPurifier_Strategy_ValidateAttributes_IDTest extends HTMLPurifier_StrategyHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_Strategy_ValidateAttributes();
+ $this->config->set('Attr.EnableID', true);
+ }
+
+
+ function testPreserveIDWhenEnabled() {
+ $this->assertResult('<div id="valid">Preserve the ID.</div>');
+ }
+
+ function testRemoveInvalidID() {
+ $this->assertResult(
+ '<div id="0invalid">Kill the ID.</div>',
+ '<div>Kill the ID.</div>'
+ );
+ }
+
+ function testRemoveDuplicateID() {
+ $this->assertResult(
+ '<div id="valid">Valid</div><div id="valid">Invalid</div>',
+ '<div id="valid">Valid</div><div>Invalid</div>'
+ );
+ }
+
+ function testAttributeKeyCaseInsensitivity() {
+ $this->assertResult(
+ '<div ID="valid">Convert ID to lowercase.</div>',
+ '<div id="valid">Convert ID to lowercase.</div>'
+ );
+ }
+
+ function testTrimWhitespace() {
+ $this->assertResult(
+ '<div id=" valid ">Trim whitespace.</div>',
+ '<div id="valid">Trim whitespace.</div>'
+ );
+ }
+
+ function testIDBlacklist() {
+ $this->config->set('Attr.IDBlacklist', array('invalid'));
+ $this->assertResult(
+ '<div id="invalid">Invalid</div>',
+ '<div>Invalid</div>'
+ );
+ }
+
+ function testNameConvertedToID() {
+ $this->config->set('HTML.TidyLevel', 'heavy');
+ $this->assertResult(
+ '<a name="foobar" />',
+ '<a id="foobar" />'
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributes_TidyTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributes_TidyTest.php
new file mode 100644
index 000000000..d4eeeebfc
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributes_TidyTest.php
@@ -0,0 +1,351 @@
+<?php
+
+class HTMLPurifier_Strategy_ValidateAttributes_TidyTest extends HTMLPurifier_StrategyHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->obj = new HTMLPurifier_Strategy_ValidateAttributes();
+ $this->config->set('HTML.TidyLevel', 'heavy');
+ }
+
+ function testConvertCenterAlign() {
+ $this->assertResult(
+ '<h1 align="center">Centered Headline</h1>',
+ '<h1 style="text-align:center;">Centered Headline</h1>'
+ );
+ }
+
+ function testConvertRightAlign() {
+ $this->assertResult(
+ '<h1 align="right">Right-aligned Headline</h1>',
+ '<h1 style="text-align:right;">Right-aligned Headline</h1>'
+ );
+ }
+
+ function testConvertLeftAlign() {
+ $this->assertResult(
+ '<h1 align="left">Left-aligned Headline</h1>',
+ '<h1 style="text-align:left;">Left-aligned Headline</h1>'
+ );
+ }
+
+ function testConvertJustifyAlign() {
+ $this->assertResult(
+ '<p align="justify">Justified Paragraph</p>',
+ '<p style="text-align:justify;">Justified Paragraph</p>'
+ );
+ }
+
+ function testRemoveInvalidAlign() {
+ $this->assertResult(
+ '<h1 align="invalid">Invalid Headline</h1>',
+ '<h1>Invalid Headline</h1>'
+ );
+ }
+
+ function testConvertTableLengths() {
+ $this->assertResult(
+ '<td width="5%" height="10" /><th width="10" height="5%" /><hr width="10" height="10" />',
+ '<td style="width:5%;height:10px;" /><th style="width:10px;height:5%;" /><hr style="width:10px;" />'
+ );
+ }
+
+ function testTdConvertNowrap() {
+ $this->assertResult(
+ '<td nowrap />',
+ '<td style="white-space:nowrap;" />'
+ );
+ }
+
+ function testCaptionConvertAlignLeft() {
+ $this->assertResult(
+ '<caption align="left" />',
+ '<caption style="text-align:left;" />'
+ );
+ }
+
+ function testCaptionConvertAlignRight() {
+ $this->assertResult(
+ '<caption align="right" />',
+ '<caption style="text-align:right;" />'
+ );
+ }
+
+ function testCaptionConvertAlignTop() {
+ $this->assertResult(
+ '<caption align="top" />',
+ '<caption style="caption-side:top;" />'
+ );
+ }
+
+ function testCaptionConvertAlignBottom() {
+ $this->assertResult(
+ '<caption align="bottom" />',
+ '<caption style="caption-side:bottom;" />'
+ );
+ }
+
+ function testCaptionRemoveInvalidAlign() {
+ $this->assertResult(
+ '<caption align="nonsense" />',
+ '<caption />'
+ );
+ }
+
+ function testTableConvertAlignLeft() {
+ $this->assertResult(
+ '<table align="left" />',
+ '<table style="float:left;" />'
+ );
+ }
+
+ function testTableConvertAlignCenter() {
+ $this->assertResult(
+ '<table align="center" />',
+ '<table style="margin-left:auto;margin-right:auto;" />'
+ );
+ }
+
+ function testTableConvertAlignRight() {
+ $this->assertResult(
+ '<table align="right" />',
+ '<table style="float:right;" />'
+ );
+ }
+
+ function testTableRemoveInvalidAlign() {
+ $this->assertResult(
+ '<table align="top" />',
+ '<table />'
+ );
+ }
+
+ function testImgConvertAlignLeft() {
+ $this->assertResult(
+ '<img src="foobar.jpg" alt="foobar" align="left" />',
+ '<img src="foobar.jpg" alt="foobar" style="float:left;" />'
+ );
+ }
+
+ function testImgConvertAlignRight() {
+ $this->assertResult(
+ '<img src="foobar.jpg" alt="foobar" align="right" />',
+ '<img src="foobar.jpg" alt="foobar" style="float:right;" />'
+ );
+ }
+
+ function testImgConvertAlignBottom() {
+ $this->assertResult(
+ '<img src="foobar.jpg" alt="foobar" align="bottom" />',
+ '<img src="foobar.jpg" alt="foobar" style="vertical-align:baseline;" />'
+ );
+ }
+
+ function testImgConvertAlignMiddle() {
+ $this->assertResult(
+ '<img src="foobar.jpg" alt="foobar" align="middle" />',
+ '<img src="foobar.jpg" alt="foobar" style="vertical-align:middle;" />'
+ );
+ }
+
+ function testImgConvertAlignTop() {
+ $this->assertResult(
+ '<img src="foobar.jpg" alt="foobar" align="top" />',
+ '<img src="foobar.jpg" alt="foobar" style="vertical-align:top;" />'
+ );
+ }
+
+ function testImgRemoveInvalidAlign() {
+ $this->assertResult(
+ '<img src="foobar.jpg" alt="foobar" align="outerspace" />',
+ '<img src="foobar.jpg" alt="foobar" />'
+ );
+ }
+
+ function testBorderConvertHVSpace() {
+ $this->assertResult(
+ '<img src="foo" alt="foo" hspace="1" vspace="3" />',
+ '<img src="foo" alt="foo" style="margin-top:3px;margin-bottom:3px;margin-left:1px;margin-right:1px;" />'
+ );
+ }
+
+ function testHrConvertSize() {
+ $this->assertResult(
+ '<hr size="3" />',
+ '<hr style="height:3px;" />'
+ );
+ }
+
+ function testHrConvertNoshade() {
+ $this->assertResult(
+ '<hr noshade />',
+ '<hr style="color:#808080;background-color:#808080;border:0;" />'
+ );
+ }
+
+ function testHrConvertAlignLeft() {
+ $this->assertResult(
+ '<hr align="left" />',
+ '<hr style="margin-left:0;margin-right:auto;text-align:left;" />'
+ );
+ }
+
+ function testHrConvertAlignCenter() {
+ $this->assertResult(
+ '<hr align="center" />',
+ '<hr style="margin-left:auto;margin-right:auto;text-align:center;" />'
+ );
+ }
+
+ function testHrConvertAlignRight() {
+ $this->assertResult(
+ '<hr align="right" />',
+ '<hr style="margin-left:auto;margin-right:0;text-align:right;" />'
+ );
+ }
+
+ function testHrRemoveInvalidAlign() {
+ $this->assertResult(
+ '<hr align="bottom" />',
+ '<hr />'
+ );
+ }
+
+ function testBrConvertClearLeft() {
+ $this->assertResult(
+ '<br clear="left" />',
+ '<br style="clear:left;" />'
+ );
+ }
+
+ function testBrConvertClearRight() {
+ $this->assertResult(
+ '<br clear="right" />',
+ '<br style="clear:right;" />'
+ );
+ }
+
+ function testBrConvertClearAll() {
+ $this->assertResult(
+ '<br clear="all" />',
+ '<br style="clear:both;" />'
+ );
+ }
+
+ function testBrConvertClearNone() {
+ $this->assertResult(
+ '<br clear="none" />',
+ '<br style="clear:none;" />'
+ );
+ }
+
+ function testBrRemoveInvalidClear() {
+ $this->assertResult(
+ '<br clear="foo" />',
+ '<br />'
+ );
+ }
+
+ function testUlConvertTypeDisc() {
+ $this->assertResult(
+ '<ul type="disc" />',
+ '<ul style="list-style-type:disc;" />'
+ );
+ }
+
+ function testUlConvertTypeSquare() {
+ $this->assertResult(
+ '<ul type="square" />',
+ '<ul style="list-style-type:square;" />'
+ );
+ }
+
+ function testUlConvertTypeCircle() {
+ $this->assertResult(
+ '<ul type="circle" />',
+ '<ul style="list-style-type:circle;" />'
+ );
+ }
+
+ function testUlConvertTypeCaseInsensitive() {
+ $this->assertResult(
+ '<ul type="CIRCLE" />',
+ '<ul style="list-style-type:circle;" />'
+ );
+ }
+
+ function testUlRemoveInvalidType() {
+ $this->assertResult(
+ '<ul type="a" />',
+ '<ul />'
+ );
+ }
+
+ function testOlConvertType1() {
+ $this->assertResult(
+ '<ol type="1" />',
+ '<ol style="list-style-type:decimal;" />'
+ );
+ }
+
+ function testOlConvertTypeLowerI() {
+ $this->assertResult(
+ '<ol type="i" />',
+ '<ol style="list-style-type:lower-roman;" />'
+ );
+ }
+
+ function testOlConvertTypeUpperI() {
+ $this->assertResult(
+ '<ol type="I" />',
+ '<ol style="list-style-type:upper-roman;" />'
+ );
+ }
+
+ function testOlConvertTypeLowerA() {
+ $this->assertResult(
+ '<ol type="a" />',
+ '<ol style="list-style-type:lower-alpha;" />'
+ );
+ }
+
+ function testOlConvertTypeUpperA() {
+ $this->assertResult(
+ '<ol type="A" />',
+ '<ol style="list-style-type:upper-alpha;" />'
+ );
+ }
+
+ function testOlRemoveInvalidType() {
+ $this->assertResult(
+ '<ol type="disc" />',
+ '<ol />'
+ );
+ }
+
+ function testLiConvertTypeCircle() {
+ $this->assertResult(
+ '<li type="circle" />',
+ '<li style="list-style-type:circle;" />'
+ );
+ }
+
+ function testLiConvertTypeA() {
+ $this->assertResult(
+ '<li type="A" />',
+ '<li style="list-style-type:upper-alpha;" />'
+ );
+ }
+
+ function testLiConvertTypeCaseSensitive() {
+ $this->assertResult(
+ '<li type="CIRCLE" />',
+ '<li />'
+ );
+ }
+
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/StrategyHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/StrategyHarness.php
new file mode 100644
index 000000000..15f22f6bd
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/StrategyHarness.php
@@ -0,0 +1,15 @@
+<?php
+
+class HTMLPurifier_StrategyHarness extends HTMLPurifier_ComplexHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->func = 'execute';
+ $this->to_tokens = true;
+ $this->to_html = true;
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/AppendMultiline.txt b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/AppendMultiline.txt
new file mode 100644
index 000000000..dfc12da3b
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/AppendMultiline.txt
@@ -0,0 +1,5 @@
+--KEY--
+Line1
+--KEY--
+Line2
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Default.txt b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Default.txt
new file mode 100644
index 000000000..9d2aea80d
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Default.txt
@@ -0,0 +1,2 @@
+DefaultValue
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Multi.txt b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Multi.txt
new file mode 100644
index 000000000..caea55b1e
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Multi.txt
@@ -0,0 +1,19 @@
+Namespace.Directive
+TYPE: string
+CHAIN-ME: 2
+--DESCRIPTION--
+Multiline
+stuff
+--FOR-WHO--
+Single multiline
+----
+
+Namespace.Directive2
+TYPE: integer
+CHAIN-ME: 3
+--DESCRIPTION--
+M
+stuff
+--FOR-WHO--
+Single multiline2
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/OverrideSingle.txt b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/OverrideSingle.txt
new file mode 100644
index 000000000..1d27c7148
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/OverrideSingle.txt
@@ -0,0 +1,3 @@
+KEY: Original
+KEY: New
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Simple.txt b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Simple.txt
new file mode 100644
index 000000000..e8042d9ed
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Simple.txt
@@ -0,0 +1,10 @@
+Namespace.Directive
+TYPE: string
+CHAIN-ME: 2
+--DESCRIPTION--
+Multiline
+stuff
+--EMPTY--
+--FOR-WHO--
+Single multiline
+--# vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/StringHashParserTest.php b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParserTest.php
new file mode 100644
index 000000000..6d357f86c
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParserTest.php
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @note Sample input files are located in the StringHashParser/ directory.
+ */
+class HTMLPurifier_StringHashParserTest extends UnitTestCase
+{
+
+ /**
+ * Instance of ConfigSchema_StringHashParser being tested.
+ */
+ protected $parser;
+
+ public function setup() {
+ $this->parser = new HTMLPurifier_StringHashParser();
+ }
+
+ /**
+ * Assert that $file gets parsed into the form of $expect
+ */
+ protected function assertParse($file, $expect) {
+ $result = $this->parser->parseFile(dirname(__FILE__) . '/StringHashParser/' . $file);
+ $this->assertIdentical($result, $expect);
+ }
+
+ function testSimple() {
+ $this->assertParse('Simple.txt', array(
+ 'ID' => 'Namespace.Directive',
+ 'TYPE' => 'string',
+ 'CHAIN-ME' => '2',
+ 'DESCRIPTION' => "Multiline\nstuff\n",
+ 'EMPTY' => '',
+ 'FOR-WHO' => "Single multiline\n",
+ ));
+ }
+
+ function testOverrideSingle() {
+ $this->assertParse('OverrideSingle.txt', array(
+ 'KEY' => 'New',
+ ));
+ }
+
+ function testAppendMultiline() {
+ $this->assertParse('AppendMultiline.txt', array(
+ 'KEY' => "Line1\nLine2\n",
+ ));
+ }
+
+ function testDefault() {
+ $this->parser->default = 'NEW-ID';
+ $this->assertParse('Default.txt', array(
+ 'NEW-ID' => 'DefaultValue',
+ ));
+ }
+
+ function testError() {
+ try {
+ $this->parser->parseFile('NoExist.txt');
+ } catch (HTMLPurifier_ConfigSchema_Exception $e) {
+ $this->assertIdentical($e->getMessage(), 'File NoExist.txt does not exist');
+ }
+ }
+
+ function testParseMultiple() {
+ $result = $this->parser->parseMultiFile(dirname(__FILE__) . '/StringHashParser/Multi.txt');
+ $this->assertIdentical(
+ $result,
+ array(
+ array(
+ 'ID' => 'Namespace.Directive',
+ 'TYPE' => 'string',
+ 'CHAIN-ME' => '2',
+ 'DESCRIPTION' => "Multiline\nstuff\n",
+ 'FOR-WHO' => "Single multiline\n",
+ ),
+ array(
+ 'ID' => 'Namespace.Directive2',
+ 'TYPE' => 'integer',
+ 'CHAIN-ME' => '3',
+ 'DESCRIPTION' => "M\nstuff\n",
+ 'FOR-WHO' => "Single multiline2\n",
+ )
+ )
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/StringHashTest.php b/lib/htmlpurifier/tests/HTMLPurifier/StringHashTest.php
new file mode 100644
index 000000000..653e5bf70
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/StringHashTest.php
@@ -0,0 +1,20 @@
+<?php
+
+class HTMLPurifier_StringHashTest extends UnitTestCase
+{
+
+ function testUsed() {
+ $hash = new HTMLPurifier_StringHash(array(
+ 'key' => 'value',
+ 'key2' => 'value2'
+ ));
+ $this->assertIdentical($hash->getAccessed(), array());
+ $t = $hash->offsetGet('key');
+ $this->assertIdentical($hash->getAccessed(), array('key' => true));
+ $hash->resetAccessed();
+ $this->assertIdentical($hash->getAccessed(), array());
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/TagTransformTest.php b/lib/htmlpurifier/tests/HTMLPurifier/TagTransformTest.php
new file mode 100644
index 000000000..8669d5b91
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/TagTransformTest.php
@@ -0,0 +1,179 @@
+<?php
+
+// needs to be seperated into files
+class HTMLPurifier_TagTransformTest extends HTMLPurifier_Harness
+{
+
+ /**
+ * Asserts that a transformation happens
+ *
+ * This assertion performs several tests on the transform:
+ *
+ * -# Transforms a start tag with only $name and no attributes
+ * -# Transforms a start tag with $name and $attributes
+ * -# Transform an end tag
+ * -# Transform an empty tag with only $name and no attributes
+ * -# Transform an empty tag with $name and $attributes
+ *
+ * In its current form, it assumes that start and empty tags would be
+ * treated the same, and is really ensuring that the tag transform doesn't
+ * do anything wonky to the tag type.
+ *
+ * @param $transformer HTMLPurifier_TagTransform class to test
+ * @param $name Name of the original tag
+ * @param $attributes Attributes of the original tag
+ * @param $expect_name Name of output tag
+ * @param $expect_attributes Attributes of output tag when $attributes
+ * is included.
+ * @param $expect_added_attributes Attributes of output tag when $attributes
+ * are omitted.
+ * @param $config_array Configuration array for HTMLPurifier_Config
+ * @param $context_array Context array for HTMLPurifier_Context
+ */
+ protected function assertTransformation($transformer,
+ $name, $attributes,
+ $expect_name, $expect_attributes,
+ $expect_added_attributes = array(),
+ $config_array = array(), $context_array = array()) {
+
+ $config = HTMLPurifier_Config::createDefault();
+ $config->loadArray($config_array);
+
+ $context = new HTMLPurifier_Context();
+ $context->loadArray($context_array);
+
+ // start tag transform
+ $this->assertIdentical(
+ new HTMLPurifier_Token_Start($expect_name, $expect_added_attributes),
+ $transformer->transform(
+ new HTMLPurifier_Token_Start($name), $config, $context)
+ );
+
+ // start tag transform with attributes
+ $this->assertIdentical(
+ new HTMLPurifier_Token_Start($expect_name, $expect_attributes),
+ $transformer->transform(
+ new HTMLPurifier_Token_Start($name, $attributes),
+ $config, $context
+ )
+ );
+
+ // end tag transform
+ $this->assertIdentical(
+ new HTMLPurifier_Token_End($expect_name),
+ $transformer->transform(
+ new HTMLPurifier_Token_End($name), $config, $context
+ )
+ );
+
+ // empty tag transform
+ $this->assertIdentical(
+ new HTMLPurifier_Token_Empty($expect_name, $expect_added_attributes),
+ $transformer->transform(
+ new HTMLPurifier_Token_Empty($name), $config, $context
+ )
+ );
+
+ // empty tag transform with attributes
+ $this->assertIdentical(
+ new HTMLPurifier_Token_Empty($expect_name, $expect_attributes),
+ $transformer->transform(
+ new HTMLPurifier_Token_Empty($name, $attributes),
+ $config, $context
+ )
+ );
+
+
+ }
+
+ function testSimple() {
+
+ $transformer = new HTMLPurifier_TagTransform_Simple('ul');
+
+ $this->assertTransformation(
+ $transformer,
+ 'menu', array('class' => 'boom'),
+ 'ul', array('class' => 'boom')
+ );
+
+ }
+
+ function testSimpleWithCSS() {
+
+ $transformer = new HTMLPurifier_TagTransform_Simple('div', 'text-align:center;');
+
+ $this->assertTransformation(
+ $transformer,
+ 'center', array('class' => 'boom', 'style'=>'font-weight:bold;'),
+ 'div', array('class' => 'boom', 'style'=>'text-align:center;font-weight:bold;'),
+ array('style'=>'text-align:center;')
+ );
+
+ // test special case, uppercase attribute key
+ $this->assertTransformation(
+ $transformer,
+ 'center', array('STYLE'=>'font-weight:bold;'),
+ 'div', array('style'=>'text-align:center;font-weight:bold;'),
+ array('style'=>'text-align:center;')
+ );
+
+ }
+
+ protected function assertSizeToStyle($transformer, $size, $style) {
+ $this->assertTransformation(
+ $transformer,
+ 'font', array('size' => $size),
+ 'span', array('style' => 'font-size:' . $style . ';')
+ );
+ }
+
+ function testFont() {
+
+ $transformer = new HTMLPurifier_TagTransform_Font();
+
+ // test a font-face transformation
+ $this->assertTransformation(
+ $transformer,
+ 'font', array('face' => 'Arial'),
+ 'span', array('style' => 'font-family:Arial;')
+ );
+
+ // test a color transformation
+ $this->assertTransformation(
+ $transformer,
+ 'font', array('color' => 'red'),
+ 'span', array('style' => 'color:red;')
+ );
+
+ // test the size transforms
+ $this->assertSizeToStyle($transformer, '0', 'xx-small');
+ $this->assertSizeToStyle($transformer, '1', 'xx-small');
+ $this->assertSizeToStyle($transformer, '2', 'small');
+ $this->assertSizeToStyle($transformer, '3', 'medium');
+ $this->assertSizeToStyle($transformer, '4', 'large');
+ $this->assertSizeToStyle($transformer, '5', 'x-large');
+ $this->assertSizeToStyle($transformer, '6', 'xx-large');
+ $this->assertSizeToStyle($transformer, '7', '300%');
+ $this->assertSizeToStyle($transformer, '-1', 'smaller');
+ $this->assertSizeToStyle($transformer, '-2', '60%');
+ $this->assertSizeToStyle($transformer, '-3', '60%');
+ $this->assertSizeToStyle($transformer, '+1', 'larger');
+ $this->assertSizeToStyle($transformer, '+2', '150%');
+ $this->assertSizeToStyle($transformer, '+3', '200%');
+ $this->assertSizeToStyle($transformer, '+4', '300%');
+ $this->assertSizeToStyle($transformer, '+5', '300%');
+ $this->assertTransformation(
+ $transformer, 'font', array('size' => ''),
+ 'span', array()
+ );
+
+ // test multiple transforms, the alphabetical ordering is important
+ $this->assertTransformation(
+ $transformer,
+ 'font', array('color' => 'red', 'face' => 'Arial', 'size' => '6'),
+ 'span', array('style' => 'color:red;font-family:Arial;font-size:xx-large;')
+ );
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/TokenFactoryTest.php b/lib/htmlpurifier/tests/HTMLPurifier/TokenFactoryTest.php
new file mode 100644
index 000000000..3eb731f83
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/TokenFactoryTest.php
@@ -0,0 +1,17 @@
+<?php
+
+class HTMLPurifier_TokenFactoryTest extends HTMLPurifier_Harness
+{
+ public function test() {
+
+ $factory = new HTMLPurifier_TokenFactory();
+
+ $regular = new HTMLPurifier_Token_Start('a', array('href' => 'about:blank'));
+ $generated = $factory->createStart('a', array('href' => 'about:blank'));
+
+ $this->assertIdentical($regular, $generated);
+
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/TokenTest.php b/lib/htmlpurifier/tests/HTMLPurifier/TokenTest.php
new file mode 100644
index 000000000..099b98603
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/TokenTest.php
@@ -0,0 +1,34 @@
+<?php
+
+class HTMLPurifier_TokenTest extends HTMLPurifier_Harness
+{
+
+ protected function assertTokenConstruction($name, $attr,
+ $expect_name = null, $expect_attr = null
+ ) {
+ if ($expect_name === null) $expect_name = $name;
+ if ($expect_attr === null) $expect_attr = $attr;
+ $token = new HTMLPurifier_Token_Start($name, $attr);
+
+ $this->assertIdentical($expect_name, $token->name);
+ $this->assertIdentical($expect_attr, $token->attr);
+ }
+
+ function testConstruct() {
+
+ // standard case
+ $this->assertTokenConstruction('a', array('href' => 'about:blank'));
+
+ // lowercase the tag's name
+ $this->assertTokenConstruction('A', array('href' => 'about:blank'),
+ 'a');
+
+ // lowercase attributes
+ $this->assertTokenConstruction('a', array('HREF' => 'about:blank'),
+ 'a', array('href' => 'about:blank'));
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URIDefinitionTest.php b/lib/htmlpurifier/tests/HTMLPurifier/URIDefinitionTest.php
new file mode 100644
index 000000000..6ab0c2a6b
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/URIDefinitionTest.php
@@ -0,0 +1,62 @@
+<?php
+
+class HTMLPurifier_URIDefinitionTest extends HTMLPurifier_URIHarness
+{
+
+ protected function createFilterMock($expect = true, $result = true, $post = false, $setup = true) {
+ static $i = 0;
+ generate_mock_once('HTMLPurifier_URIFilter');
+ $mock = new HTMLPurifier_URIFilterMock();
+ if ($expect) $mock->expectOnce('filter');
+ else $mock->expectNever('filter');
+ $mock->setReturnValue('filter', $result);
+ $mock->setReturnValue('prepare', $setup);
+ $mock->name = $i++;
+ $mock->post = $post;
+ return $mock;
+ }
+
+ function test_filter() {
+ $def = new HTMLPurifier_URIDefinition();
+ $def->addFilter($this->createFilterMock(), $this->config);
+ $def->addFilter($this->createFilterMock(), $this->config);
+ $uri = $this->createURI('test');
+ $this->assertTrue($def->filter($uri, $this->config, $this->context));
+ }
+
+ function test_filter_earlyAbortIfFail() {
+ $def = new HTMLPurifier_URIDefinition();
+ $def->addFilter($this->createFilterMock(true, false), $this->config);
+ $def->addFilter($this->createFilterMock(false), $this->config); // never called
+ $uri = $this->createURI('test');
+ $this->assertFalse($def->filter($uri, $this->config, $this->context));
+ }
+
+ function test_setupMemberVariables_collisionPrecedenceIsHostBaseScheme() {
+ $this->config->set('URI.Host', $host = 'example.com');
+ $this->config->set('URI.Base', $base = 'http://sub.example.com/foo/bar.html');
+ $this->config->set('URI.DefaultScheme', 'ftp');
+ $def = new HTMLPurifier_URIDefinition();
+ $def->setup($this->config);
+ $this->assertIdentical($def->host, $host);
+ $this->assertIdentical($def->base, $this->createURI($base));
+ $this->assertIdentical($def->defaultScheme, 'http'); // not ftp!
+ }
+
+ function test_setupMemberVariables_onlyScheme() {
+ $this->config->set('URI.DefaultScheme', 'ftp');
+ $def = new HTMLPurifier_URIDefinition();
+ $def->setup($this->config);
+ $this->assertIdentical($def->defaultScheme, 'ftp');
+ }
+
+ function test_setupMemberVariables_onlyBase() {
+ $this->config->set('URI.Base', 'http://sub.example.com/foo/bar.html');
+ $def = new HTMLPurifier_URIDefinition();
+ $def->setup($this->config);
+ $this->assertIdentical($def->host, 'sub.example.com');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableExternalResourcesTest.php b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableExternalResourcesTest.php
new file mode 100644
index 000000000..21c70c145
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableExternalResourcesTest.php
@@ -0,0 +1,23 @@
+<?php
+
+class HTMLPurifier_URIFilter_DisableExternalResourcesTest extends
+ HTMLPurifier_URIFilter_DisableExternalTest
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->filter = new HTMLPurifier_URIFilter_DisableExternalResources();
+ $var = true;
+ $this->context->register('EmbeddedURI', $var);
+ }
+
+ function testPreserveWhenNotEmbedded() {
+ $this->context->destroy('EmbeddedURI'); // undo setUp
+ $this->assertFiltering(
+ 'http://example.com'
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableExternalTest.php b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableExternalTest.php
new file mode 100644
index 000000000..3176246a1
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableExternalTest.php
@@ -0,0 +1,53 @@
+<?php
+
+class HTMLPurifier_URIFilter_DisableExternalTest extends HTMLPurifier_URIFilterHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->filter = new HTMLPurifier_URIFilter_DisableExternal();
+ }
+
+ function testRemoveExternal() {
+ $this->assertFiltering(
+ 'http://example.com', false
+ );
+ }
+
+ function testPreserveInternal() {
+ $this->assertFiltering(
+ '/foo/bar'
+ );
+ }
+
+ function testPreserveOurHost() {
+ $this->config->set('URI.Host', 'example.com');
+ $this->assertFiltering(
+ 'http://example.com'
+ );
+ }
+
+ function testPreserveOurSubdomain() {
+ $this->config->set('URI.Host', 'example.com');
+ $this->assertFiltering(
+ 'http://www.example.com'
+ );
+ }
+
+ function testRemoveSuperdomain() {
+ $this->config->set('URI.Host', 'www.example.com');
+ $this->assertFiltering(
+ 'http://example.com', false
+ );
+ }
+
+ function testBaseAsHost() {
+ $this->config->set('URI.Base', 'http://www.example.com/foo/bar');
+ $this->assertFiltering(
+ 'http://www.example.com/baz'
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableResourcesTest.php b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableResourcesTest.php
new file mode 100644
index 000000000..c2cea8fe6
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableResourcesTest.php
@@ -0,0 +1,24 @@
+<?php
+
+class HTMLPurifier_URIFilter_DisableResourcesTest extends HTMLPurifier_URIFilterHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->filter = new HTMLPurifier_URIFilter_DisableResources();
+ $var = true;
+ $this->context->register('EmbeddedURI', $var);
+ }
+
+ function testRemoveResource() {
+ $this->assertFiltering('/foo/bar', false);
+ }
+
+ function testPreserveRegular() {
+ $this->context->destroy('EmbeddedURI'); // undo setUp
+ $this->assertFiltering('/foo/bar');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/HostBlacklistTest.php b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/HostBlacklistTest.php
new file mode 100644
index 000000000..4af5a6009
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/HostBlacklistTest.php
@@ -0,0 +1,29 @@
+<?php
+
+class HTMLPurifier_URIFilter_HostBlacklistTest extends HTMLPurifier_URIFilterHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->filter = new HTMLPurifier_URIFilter_HostBlacklist();
+ }
+
+ function testRejectBlacklistedHost() {
+ $this->config->set('URI.HostBlacklist', 'example.com');
+ $this->assertFiltering('http://example.com', false);
+ }
+
+ function testRejectBlacklistedHostThoughNotTrue() {
+ // maybe this behavior should change
+ $this->config->set('URI.HostBlacklist', 'example.com');
+ $this->assertFiltering('http://example.comcast.com', false);
+ }
+
+ function testPreserveNonBlacklistedHost() {
+ $this->config->set('URI.HostBlacklist', 'example.com');
+ $this->assertFiltering('http://google.com');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/MakeAbsoluteTest.php b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/MakeAbsoluteTest.php
new file mode 100644
index 000000000..9002f1ff8
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/MakeAbsoluteTest.php
@@ -0,0 +1,146 @@
+<?php
+
+class HTMLPurifier_URIFilter_MakeAbsoluteTest extends HTMLPurifier_URIFilterHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->filter = new HTMLPurifier_URIFilter_MakeAbsolute();
+ $this->setBase();
+ }
+
+ function setBase($base = 'http://example.com/foo/bar.html?q=s#frag') {
+ $this->config->set('URI.Base', $base);
+ }
+
+ // corresponding to RFC 2396
+
+ function testPreserveAbsolute() {
+ $this->assertFiltering('http://example.com/foo.html');
+ }
+
+ function testFilterBlank() {
+ $this->assertFiltering('', 'http://example.com/foo/bar.html?q=s');
+ }
+
+ function testFilterEmptyPath() {
+ $this->assertFiltering('?q=s#frag', 'http://example.com/foo/bar.html?q=s#frag');
+ }
+
+ function testPreserveAltScheme() {
+ $this->assertFiltering('mailto:bob@example.com');
+ }
+
+ function testFilterIgnoreHTTPSpecialCase() {
+ $this->assertFiltering('http:/', 'http://example.com/');
+ }
+
+ function testFilterAbsolutePath() {
+ $this->assertFiltering('/foo.txt', 'http://example.com/foo.txt');
+ }
+
+ function testFilterRelativePath() {
+ $this->assertFiltering('baz.txt', 'http://example.com/foo/baz.txt');
+ }
+
+ function testFilterRelativePathWithInternalDot() {
+ $this->assertFiltering('./baz.txt', 'http://example.com/foo/baz.txt');
+ }
+
+ function testFilterRelativePathWithEndingDot() {
+ $this->assertFiltering('baz/.', 'http://example.com/foo/baz/');
+ }
+
+ function testFilterRelativePathDot() {
+ $this->assertFiltering('.', 'http://example.com/foo/');
+ }
+
+ function testFilterRelativePathMultiDot() {
+ $this->assertFiltering('././foo/./bar/.././baz', 'http://example.com/foo/foo/baz');
+ }
+
+ function testFilterAbsolutePathWithDot() {
+ $this->assertFiltering('/./foo', 'http://example.com/foo');
+ }
+
+ function testFilterAbsolutePathWithMultiDot() {
+ $this->assertFiltering('/./foo/../bar/.', 'http://example.com/bar/');
+ }
+
+ function testFilterRelativePathWithInternalDotDot() {
+ $this->assertFiltering('../baz.txt', 'http://example.com/baz.txt');
+ }
+
+ function testFilterRelativePathWithEndingDotDot() {
+ $this->assertFiltering('..', 'http://example.com/');
+ }
+
+ function testFilterRelativePathTooManyDotDots() {
+ $this->assertFiltering('../../', 'http://example.com/');
+ }
+
+ function testFilterAppendingQueryAndFragment() {
+ $this->assertFiltering('/foo.php?q=s#frag', 'http://example.com/foo.php?q=s#frag');
+ }
+
+ // edge cases below
+
+ function testFilterAbsolutePathBase() {
+ $this->setBase('/foo/baz.txt');
+ $this->assertFiltering('test.php', '/foo/test.php');
+ }
+
+ function testFilterAbsolutePathBaseDirectory() {
+ $this->setBase('/foo/');
+ $this->assertFiltering('test.php', '/foo/test.php');
+ }
+
+ function testFilterAbsolutePathBaseBelow() {
+ $this->setBase('/foo/baz.txt');
+ $this->assertFiltering('../../test.php', '/test.php');
+ }
+
+ function testFilterRelativePathBase() {
+ $this->setBase('foo/baz.html');
+ $this->assertFiltering('foo.php', 'foo/foo.php');
+ }
+
+ function testFilterRelativePathBaseBelow() {
+ $this->setBase('../baz.html');
+ $this->assertFiltering('test/strike.html', '../test/strike.html');
+ }
+
+ function testFilterRelativePathBaseWithAbsoluteURI() {
+ $this->setBase('../baz.html');
+ $this->assertFiltering('/test/strike.html');
+ }
+
+ function testFilterRelativePathBaseWithDot() {
+ $this->setBase('../baz.html');
+ $this->assertFiltering('.', '../');
+ }
+
+ function testRemoveJavaScriptWithEmbeddedLink() {
+ // credits: NykO18
+ $this->setBase('http://www.example.com/');
+ $this->assertFiltering('javascript: window.location = \'http://www.example.com\';', false);
+ }
+
+ // miscellaneous
+
+ function testFilterDomainWithNoSlash() {
+ $this->setBase('http://example.com');
+ $this->assertFiltering('foo', 'http://example.com/foo');
+ }
+
+ // error case
+
+ function testErrorNoBase() {
+ $this->setBase(null);
+ $this->expectError('URI.MakeAbsolute is being ignored due to lack of value for URI.Base configuration');
+ $this->assertFiltering('foo/bar.txt');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/MungeTest.php b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/MungeTest.php
new file mode 100644
index 000000000..1acf11ea7
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/MungeTest.php
@@ -0,0 +1,145 @@
+<?php
+
+class HTMLPurifier_URIFilter_MungeTest extends HTMLPurifier_URIFilterHarness
+{
+
+ function setUp() {
+ parent::setUp();
+ $this->filter = new HTMLPurifier_URIFilter_Munge();
+ }
+
+ protected function setMunge($uri = 'http://www.google.com/url?q=%s') {
+ $this->config->set('URI.Munge', $uri);
+ }
+
+ protected function setSecureMunge($key = 'secret') {
+ $this->setMunge('/redirect.php?url=%s&checksum=%t');
+ $this->config->set('URI.MungeSecretKey', $key);
+ }
+
+ function testMunge() {
+ $this->setMunge();
+ $this->assertFiltering(
+ 'http://www.example.com/',
+ 'http://www.google.com/url?q=http%3A%2F%2Fwww.example.com%2F'
+ );
+ }
+
+ function testMungeReplaceTagName() {
+ $this->setMunge('/r?tagname=%n&url=%s');
+ $token = new HTMLPurifier_Token_Start('a');
+ $this->context->register('CurrentToken', $token);
+ $this->assertFiltering('http://google.com', '/r?tagname=a&url=http%3A%2F%2Fgoogle.com');
+ }
+
+ function testMungeReplaceAttribute() {
+ $this->setMunge('/r?attr=%m&url=%s');
+ $attr = 'href';
+ $this->context->register('CurrentAttr', $attr);
+ $this->assertFiltering('http://google.com', '/r?attr=href&url=http%3A%2F%2Fgoogle.com');
+ }
+
+ function testMungeReplaceResource() {
+ $this->setMunge('/r?embeds=%r&url=%s');
+ $embeds = false;
+ $this->context->register('EmbeddedURI', $embeds);
+ $this->assertFiltering('http://google.com', '/r?embeds=&url=http%3A%2F%2Fgoogle.com');
+ }
+
+ function testMungeReplaceCSSProperty() {
+ $this->setMunge('/r?property=%p&url=%s');
+ $property = 'background';
+ $this->context->register('CurrentCSSProperty', $property);
+ $this->assertFiltering('http://google.com', '/r?property=background&url=http%3A%2F%2Fgoogle.com');
+ }
+
+ function testIgnoreEmbedded() {
+ $this->setMunge();
+ $embeds = true;
+ $this->context->register('EmbeddedURI', $embeds);
+ $this->assertFiltering('http://example.com');
+ }
+
+ function testProcessEmbedded() {
+ $this->setMunge();
+ $this->config->set('URI.MungeResources', true);
+ $embeds = true;
+ $this->context->register('EmbeddedURI', $embeds);
+ $this->assertFiltering('http://www.example.com/', 'http://www.google.com/url?q=http%3A%2F%2Fwww.example.com%2F');
+ }
+
+ function testPreserveRelative() {
+ $this->setMunge();
+ $this->assertFiltering('index.html');
+ }
+
+ function testMungeIgnoreUnknownSchemes() {
+ $this->setMunge();
+ $this->assertFiltering('javascript:foobar();', true);
+ }
+
+ function testSecureMungePreserve() {
+ $this->setSecureMunge();
+ $this->assertFiltering('/local');
+ }
+
+ function testSecureMungePreserveEmbedded() {
+ $this->setSecureMunge();
+ $embedded = true;
+ $this->context->register('EmbeddedURI', $embedded);
+ $this->assertFiltering('http://google.com');
+ }
+
+ function testSecureMungeStandard() {
+ $this->setSecureMunge();
+ $this->assertFiltering('http://google.com', '/redirect.php?url=http%3A%2F%2Fgoogle.com&checksum=0072e2f817fd2844825def74e54443debecf0892');
+ }
+
+ function testSecureMungeIgnoreUnknownSchemes() {
+ // This should be integration tested as well to be false
+ $this->setSecureMunge();
+ $this->assertFiltering('javascript:', true);
+ }
+
+ function testSecureMungeIgnoreUnbrowsableSchemes() {
+ $this->setSecureMunge();
+ $this->assertFiltering('news:', true);
+ }
+
+ function testSecureMungeToDirectory() {
+ $this->setSecureMunge();
+ $this->setMunge('/links/%s/%t');
+ $this->assertFiltering('http://google.com', '/links/http%3A%2F%2Fgoogle.com/0072e2f817fd2844825def74e54443debecf0892');
+ }
+
+ function testMungeIgnoreSameDomain() {
+ $this->setMunge('http://example.com/%s');
+ $this->assertFiltering('http://example.com/foobar');
+ }
+
+ function testMungeIgnoreSameDomainInsecureToSecure() {
+ $this->setMunge('http://example.com/%s');
+ $this->assertFiltering('https://example.com/foobar');
+ }
+
+ function testMungeIgnoreSameDomainSecureToSecure() {
+ $this->config->set('URI.Base', 'https://example.com');
+ $this->setMunge('http://example.com/%s');
+ $this->assertFiltering('https://example.com/foobar');
+ }
+
+ function testMungeSameDomainSecureToInsecure() {
+ $this->config->set('URI.Base', 'https://example.com');
+ $this->setMunge('/%s');
+ $this->assertFiltering('http://example.com/foobar', '/http%3A%2F%2Fexample.com%2Ffoobar');
+ }
+
+ function testMungeIgnoresSourceHost() {
+ $this->config->set('URI.Host', 'foo.example.com');
+ $this->setMunge('http://example.com/%s');
+ $this->assertFiltering('http://foo.example.com/bar');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URIFilterHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/URIFilterHarness.php
new file mode 100644
index 000000000..165ae6788
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/URIFilterHarness.php
@@ -0,0 +1,15 @@
+<?php
+
+class HTMLPurifier_URIFilterHarness extends HTMLPurifier_URIHarness
+{
+
+ protected function assertFiltering($uri, $expect_uri = true) {
+ $this->prepareURI($uri, $expect_uri);
+ $this->filter->prepare($this->config, $this->context);
+ $result = $this->filter->filter($uri, $this->config, $this->context);
+ $this->assertEitherFailOrIdentical($result, $uri, $expect_uri);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URIHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/URIHarness.php
new file mode 100644
index 000000000..852460a85
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/URIHarness.php
@@ -0,0 +1,31 @@
+<?php
+
+class HTMLPurifier_URIHarness extends HTMLPurifier_Harness
+{
+
+ /**
+ * Prepares two URIs into object form
+ * @param &$uri Reference to string input URI
+ * @param &$expect_uri Reference to string expectation URI
+ * @note If $expect_uri is false, it will stay false
+ */
+ protected function prepareURI(&$uri, &$expect_uri) {
+ $parser = new HTMLPurifier_URIParser();
+ if ($expect_uri === true) $expect_uri = $uri;
+ $uri = $parser->parse($uri);
+ if ($expect_uri !== false) {
+ $expect_uri = $parser->parse($expect_uri);
+ }
+ }
+
+ /**
+ * Generates a URI object from the corresponding string
+ */
+ protected function createURI($uri) {
+ $parser = new HTMLPurifier_URIParser();
+ return $parser->parse($uri);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URIParserTest.php b/lib/htmlpurifier/tests/HTMLPurifier/URIParserTest.php
new file mode 100644
index 000000000..5cb5850f7
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/URIParserTest.php
@@ -0,0 +1,145 @@
+<?php
+
+class HTMLPurifier_URIParserTest extends HTMLPurifier_Harness
+{
+
+ protected function assertParsing(
+ $uri, $scheme, $userinfo, $host, $port, $path, $query, $fragment, $config = null, $context = null
+ ) {
+ $this->prepareCommon($config, $context);
+ $parser = new HTMLPurifier_URIParser();
+ $result = $parser->parse($uri, $config, $context);
+ $expect = new HTMLPurifier_URI($scheme, $userinfo, $host, $port, $path, $query, $fragment);
+ $this->assertEqual($result, $expect);
+ }
+
+ function testPercentNormalization() {
+ $this->assertParsing(
+ '%G',
+ null, null, null, null, '%25G', null, null
+ );
+ }
+
+ function testRegular() {
+ $this->assertParsing(
+ 'http://www.example.com/webhp?q=foo#result2',
+ 'http', null, 'www.example.com', null, '/webhp', 'q=foo', 'result2'
+ );
+ }
+
+ function testPortAndUsername() {
+ $this->assertParsing(
+ 'http://user@authority.part:80/now/the/path?query#fragment',
+ 'http', 'user', 'authority.part', 80, '/now/the/path', 'query', 'fragment'
+ );
+ }
+
+ function testPercentEncoding() {
+ $this->assertParsing(
+ 'http://en.wikipedia.org/wiki/Clich%C3%A9',
+ 'http', null, 'en.wikipedia.org', null, '/wiki/Clich%C3%A9', null, null
+ );
+ }
+
+ function testEmptyQuery() {
+ $this->assertParsing(
+ 'http://www.example.com/?#',
+ 'http', null, 'www.example.com', null, '/', '', null
+ );
+ }
+
+ function testEmptyPath() {
+ $this->assertParsing(
+ 'http://www.example.com',
+ 'http', null, 'www.example.com', null, '', null, null
+ );
+ }
+
+ function testOpaqueURI() {
+ $this->assertParsing(
+ 'mailto:bob@example.com',
+ 'mailto', null, null, null, 'bob@example.com', null, null
+ );
+ }
+
+ function testIPv4Address() {
+ $this->assertParsing(
+ 'http://192.0.34.166/',
+ 'http', null, '192.0.34.166', null, '/', null, null
+ );
+ }
+
+ function testFakeIPv4Address() {
+ $this->assertParsing(
+ 'http://333.123.32.123/',
+ 'http', null, '333.123.32.123', null, '/', null, null
+ );
+ }
+
+ function testIPv6Address() {
+ $this->assertParsing(
+ 'http://[2001:db8::7]/c=GB?objectClass?one',
+ 'http', null, '[2001:db8::7]', null, '/c=GB', 'objectClass?one', null
+ );
+ }
+
+ function testInternationalizedDomainName() {
+ $this->assertParsing(
+ "http://t\xC5\xABdali\xC5\x86.lv",
+ 'http', null, "t\xC5\xABdali\xC5\x86.lv", null, '', null, null
+ );
+ }
+
+ function testInvalidPort() {
+ $this->assertParsing(
+ 'http://example.com:foobar',
+ 'http', null, 'example.com', null, '', null, null
+ );
+ }
+
+ function testPathAbsolute() {
+ $this->assertParsing(
+ 'http:/this/is/path',
+ 'http', null, null, null, '/this/is/path', null, null
+ );
+ }
+
+ function testPathRootless() {
+ // this should not be used but is allowed
+ $this->assertParsing(
+ 'http:this/is/path',
+ 'http', null, null, null, 'this/is/path', null, null
+ );
+ }
+
+ function testPathEmpty() {
+ $this->assertParsing(
+ 'http:',
+ 'http', null, null, null, '', null, null
+ );
+ }
+
+ function testRelativeURI() {
+ $this->assertParsing(
+ '/a/b',
+ null, null, null, null, '/a/b', null, null
+ );
+ }
+
+ function testMalformedTag() {
+ $this->assertParsing(
+ 'http://www.example.com/>',
+ 'http', null, 'www.example.com', null, '/', null, null
+ );
+ }
+
+ function testEmpty() {
+ $this->assertParsing(
+ '',
+ null, null, null, null, '', null, null
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URISchemeRegistryTest.php b/lib/htmlpurifier/tests/HTMLPurifier/URISchemeRegistryTest.php
new file mode 100644
index 000000000..e124aa18d
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/URISchemeRegistryTest.php
@@ -0,0 +1,49 @@
+<?php
+
+class HTMLPurifier_URISchemeRegistryTest extends HTMLPurifier_Harness
+{
+
+ function test() {
+
+ generate_mock_once('HTMLPurifier_URIScheme');
+
+ $config = HTMLPurifier_Config::create(array(
+ 'URI.AllowedSchemes' => 'http, telnet',
+ 'URI.OverrideAllowedSchemes' => true
+ ));
+ $context = new HTMLPurifier_Context();
+
+ $registry = new HTMLPurifier_URISchemeRegistry();
+ $this->assertIsA($registry->getScheme('http', $config, $context), 'HTMLPurifier_URIScheme_http');
+
+ $scheme_http = new HTMLPurifier_URISchemeMock();
+ $scheme_telnet = new HTMLPurifier_URISchemeMock();
+ $scheme_foobar = new HTMLPurifier_URISchemeMock();
+
+ // register a new scheme
+ $registry->register('telnet', $scheme_telnet);
+ $this->assertIdentical($registry->getScheme('telnet', $config, $context), $scheme_telnet);
+
+ // overload a scheme, this is FINAL (forget about defaults)
+ $registry->register('http', $scheme_http);
+ $this->assertIdentical($registry->getScheme('http', $config, $context), $scheme_http);
+
+ // when we register a scheme, it's automatically allowed
+ $registry->register('foobar', $scheme_foobar);
+ $this->assertIdentical($registry->getScheme('foobar', $config, $context), $scheme_foobar);
+
+ // now, test when overriding is not allowed
+ $config = HTMLPurifier_Config::create(array(
+ 'URI.AllowedSchemes' => 'http, telnet',
+ 'URI.OverrideAllowedSchemes' => false
+ ));
+ $this->assertNull($registry->getScheme('foobar', $config, $context));
+
+ // scheme not allowed and never registered
+ $this->assertNull($registry->getScheme('ftp', $config, $context));
+
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URISchemeTest.php b/lib/htmlpurifier/tests/HTMLPurifier/URISchemeTest.php
new file mode 100644
index 000000000..4b43310bb
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/URISchemeTest.php
@@ -0,0 +1,188 @@
+<?php
+
+// WARNING: All the URI schemes are far to relaxed, we need to tighten
+// the checks.
+
+class HTMLPurifier_URISchemeTest extends HTMLPurifier_URIHarness
+{
+
+ private $pngBase64;
+
+ public function __construct() {
+ $this->pngBase64 =
+ 'iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGP'.
+ 'C/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IA'.
+ 'AAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1J'.
+ 'REFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jq'.
+ 'ch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0'.
+ 'vr4MkhoXe0rZigAAAABJRU5ErkJggg==';
+ }
+
+ protected function assertValidation($uri, $expect_uri = true) {
+ $this->prepareURI($uri, $expect_uri);
+ $this->config->set('URI.AllowedSchemes', array($uri->scheme));
+ // convenience hack: the scheme should be explicitly specified
+ $scheme = $uri->getSchemeObj($this->config, $this->context);
+ $result = $scheme->validate($uri, $this->config, $this->context);
+ $this->assertEitherFailOrIdentical($result, $uri, $expect_uri);
+ }
+
+ function test_http_regular() {
+ $this->assertValidation(
+ 'http://example.com/?s=q#fragment'
+ );
+ }
+
+ function test_http_removeDefaultPort() {
+ $this->assertValidation(
+ 'http://example.com:80',
+ 'http://example.com'
+ );
+ }
+
+ function test_http_removeUserInfo() {
+ $this->assertValidation(
+ 'http://bob@example.com',
+ 'http://example.com'
+ );
+ }
+
+ function test_http_preserveNonDefaultPort() {
+ $this->assertValidation(
+ 'http://example.com:8080'
+ );
+ }
+
+ function test_https_regular() {
+ $this->assertValidation(
+ 'https://user@example.com:443/?s=q#frag',
+ 'https://example.com/?s=q#frag'
+ );
+ }
+
+ function test_ftp_regular() {
+ $this->assertValidation(
+ 'ftp://user@example.com/path'
+ );
+ }
+
+ function test_ftp_removeDefaultPort() {
+ $this->assertValidation(
+ 'ftp://example.com:21',
+ 'ftp://example.com'
+ );
+ }
+
+ function test_ftp_removeQueryString() {
+ $this->assertValidation(
+ 'ftp://example.com?s=q',
+ 'ftp://example.com'
+ );
+ }
+
+ function test_ftp_preserveValidTypecode() {
+ $this->assertValidation(
+ 'ftp://example.com/file.txt;type=a'
+ );
+ }
+
+ function test_ftp_removeInvalidTypecode() {
+ $this->assertValidation(
+ 'ftp://example.com/file.txt;type=z',
+ 'ftp://example.com/file.txt'
+ );
+ }
+
+ function test_ftp_encodeExtraSemicolons() {
+ $this->assertValidation(
+ 'ftp://example.com/too;many;semicolons=1',
+ 'ftp://example.com/too%3Bmany%3Bsemicolons=1'
+ );
+ }
+
+ function test_news_regular() {
+ $this->assertValidation(
+ 'news:gmane.science.linguistics'
+ );
+ }
+
+ function test_news_explicit() {
+ $this->assertValidation(
+ 'news:642@eagle.ATT.COM'
+ );
+ }
+
+ function test_news_removeNonPathComponents() {
+ $this->assertValidation(
+ 'news://user@example.com:80/rec.music?path=foo#frag',
+ 'news:/rec.music#frag'
+ );
+ }
+
+ function test_nntp_regular() {
+ $this->assertValidation(
+ 'nntp://news.example.com/alt.misc/42#frag'
+ );
+ }
+
+ function test_nntp_removalOfRedundantOrUselessComponents() {
+ $this->assertValidation(
+ 'nntp://user@news.example.com:119/alt.misc/42?s=q#frag',
+ 'nntp://news.example.com/alt.misc/42#frag'
+ );
+ }
+
+ function test_mailto_regular() {
+ $this->assertValidation(
+ 'mailto:bob@example.com'
+ );
+ }
+
+ function test_mailto_removalOfRedundantOrUselessComponents() {
+ $this->assertValidation(
+ 'mailto://user@example.com:80/bob@example.com?subject=Foo#frag',
+ 'mailto:/bob@example.com?subject=Foo#frag'
+ );
+ }
+
+ function test_data_png() {
+ $this->assertValidation(
+ 'data:image/png;base64,'.$this->pngBase64
+ );
+ }
+
+ function test_data_malformed() {
+ $this->assertValidation(
+ 'data:image/png;base64,vr4MkhoXJRU5ErkJggg==',
+ false
+ );
+ }
+
+ function test_data_implicit() {
+ $this->assertValidation(
+ 'data:base64,'.$this->pngBase64,
+ 'data:image/png;base64,'.$this->pngBase64
+ );
+ }
+
+ function test_file_basic() {
+ $this->assertValidation(
+ 'file://user@MYCOMPUTER:12/foo/bar?baz#frag',
+ 'file://MYCOMPUTER/foo/bar#frag'
+ );
+ }
+
+ function test_file_local() {
+ $this->assertValidation(
+ 'file:///foo/bar?baz#frag',
+ 'file:///foo/bar#frag'
+ );
+ }
+
+ function test_ftp_empty_host() {
+ $this->assertValidation('ftp:///example.com', false);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URITest.php b/lib/htmlpurifier/tests/HTMLPurifier/URITest.php
new file mode 100644
index 000000000..02b950133
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/URITest.php
@@ -0,0 +1,201 @@
+<?php
+
+class HTMLPurifier_URITest extends HTMLPurifier_URIHarness
+{
+
+ protected function createURI($uri) {
+ $parser = new HTMLPurifier_URIParser();
+ return $parser->parse($uri);
+ }
+
+ function test_construct() {
+ $uri1 = new HTMLPurifier_URI('HTTP', 'bob', 'example.com', '23', '/foo', 'bar=2', 'slash');
+ $uri2 = new HTMLPurifier_URI('http', 'bob', 'example.com', 23, '/foo', 'bar=2', 'slash');
+ $this->assertIdentical($uri1, $uri2);
+ }
+
+ protected $oldRegistry;
+
+ protected function &setUpSchemeRegistryMock() {
+ $this->oldRegistry = HTMLPurifier_URISchemeRegistry::instance();
+ generate_mock_once('HTMLPurifier_URIScheme');
+ generate_mock_once('HTMLPurifier_URISchemeRegistry');
+ $registry = HTMLPurifier_URISchemeRegistry::instance(
+ new HTMLPurifier_URISchemeRegistryMock()
+ );
+ return $registry;
+ }
+
+ protected function setUpSchemeMock($name) {
+ $registry = $this->setUpSchemeRegistryMock();
+ $scheme_mock = new HTMLPurifier_URISchemeMock();
+ $registry->setReturnValue('getScheme', $scheme_mock, array($name, '*', '*'));
+ return $scheme_mock;
+ }
+
+ protected function setUpNoValidSchemes() {
+ $registry = $this->setUpSchemeRegistryMock();
+ $registry->setReturnValue('getScheme', false, array('*', '*', '*'));
+ }
+
+ protected function tearDownSchemeRegistryMock() {
+ HTMLPurifier_URISchemeRegistry::instance($this->oldRegistry);
+ }
+
+ function test_getSchemeObj() {
+ $scheme_mock = $this->setUpSchemeMock('http');
+
+ $uri = $this->createURI('http:');
+ $scheme_obj = $uri->getSchemeObj($this->config, $this->context);
+ $this->assertIdentical($scheme_obj, $scheme_mock);
+
+ $this->tearDownSchemeRegistryMock();
+ }
+
+ function test_getSchemeObj_invalidScheme() {
+ $this->setUpNoValidSchemes();
+
+ $uri = $this->createURI('http:');
+ $result = $uri->getSchemeObj($this->config, $this->context);
+ $this->assertIdentical($result, false);
+
+ $this->tearDownSchemeRegistryMock();
+ }
+
+ function test_getSchemaObj_defaultScheme() {
+ $scheme = 'foobar';
+
+ $scheme_mock = $this->setUpSchemeMock($scheme);
+ $this->config->set('URI.DefaultScheme', $scheme);
+
+ $uri = $this->createURI('hmm');
+ $scheme_obj = $uri->getSchemeObj($this->config, $this->context);
+ $this->assertIdentical($scheme_obj, $scheme_mock);
+
+ $this->tearDownSchemeRegistryMock();
+ }
+
+ function test_getSchemaObj_invalidDefaultScheme() {
+ $this->setUpNoValidSchemes();
+ $this->config->set('URI.DefaultScheme', 'foobar');
+
+ $uri = $this->createURI('hmm');
+
+ $this->expectError('Default scheme object "foobar" was not readable');
+ $result = $uri->getSchemeObj($this->config, $this->context);
+ $this->assertIdentical($result, false);
+
+ $this->tearDownSchemeRegistryMock();
+ }
+
+ protected function assertToString($expect_uri, $scheme, $userinfo, $host, $port, $path, $query, $fragment) {
+ $uri = new HTMLPurifier_URI($scheme, $userinfo, $host, $port, $path, $query, $fragment);
+ $string = $uri->toString();
+ $this->assertIdentical($string, $expect_uri);
+ }
+
+ function test_toString_full() {
+ $this->assertToString(
+ 'http://bob@example.com:300/foo?bar=baz#fragment',
+ 'http', 'bob', 'example.com', 300, '/foo', 'bar=baz', 'fragment'
+ );
+ }
+
+ function test_toString_scheme() {
+ $this->assertToString(
+ 'http:',
+ 'http', null, null, null, '', null, null
+ );
+ }
+
+ function test_toString_authority() {
+ $this->assertToString(
+ '//bob@example.com:8080',
+ null, 'bob', 'example.com', 8080, '', null, null
+ );
+ }
+
+ function test_toString_path() {
+ $this->assertToString(
+ '/path/to',
+ null, null, null, null, '/path/to', null, null
+ );
+ }
+
+ function test_toString_query() {
+ $this->assertToString(
+ '?q=string',
+ null, null, null, null, '', 'q=string', null
+ );
+ }
+
+ function test_toString_fragment() {
+ $this->assertToString(
+ '#fragment',
+ null, null, null, null, '', null, 'fragment'
+ );
+ }
+
+ protected function assertValidation($uri, $expect_uri = true) {
+ if ($expect_uri === true) $expect_uri = $uri;
+ $uri = $this->createURI($uri);
+ $result = $uri->validate($this->config, $this->context);
+ if ($expect_uri === false) {
+ $this->assertFalse($result);
+ } else {
+ $this->assertTrue($result);
+ $this->assertIdentical($uri->toString(), $expect_uri);
+ }
+ }
+
+ function test_validate_overlongPort() {
+ $this->assertValidation('http://example.com:65536', 'http://example.com');
+ }
+
+ function test_validate_zeroPort() {
+ $this->assertValidation('http://example.com:00', 'http://example.com');
+ }
+
+ function test_validate_invalidHostThatLooksLikeIPv6() {
+ $this->assertValidation('http://[2001:0db8:85z3:08d3:1319:8a2e:0370:7334]', '');
+ }
+
+ function test_validate_removeRedundantScheme() {
+ $this->assertValidation('http:foo:/:', 'foo%3A/:');
+ }
+
+ function test_validate_username() {
+ $this->assertValidation("http://user\xE3\x91\x94:@foo.com", 'http://user%E3%91%94:@foo.com');
+ }
+
+ function test_validate_path_abempty() {
+ $this->assertValidation("http://host/\xE3\x91\x94:", 'http://host/%E3%91%94:');
+ }
+
+ function test_validate_path_absolute() {
+ $this->assertValidation("/\xE3\x91\x94:", '/%E3%91%94:');
+ }
+
+ function test_validate_path_rootless() {
+ $this->assertValidation("mailto:\xE3\x91\x94:", 'mailto:%E3%91%94:');
+ }
+
+ function test_validate_path_noscheme() {
+ $this->assertValidation("\xE3\x91\x94", '%E3%91%94');
+ }
+
+ function test_validate_query() {
+ $this->assertValidation("?/\xE3\x91\x94", '?/%E3%91%94');
+ }
+
+ function test_validate_fragment() {
+ $this->assertValidation("#/\xE3\x91\x94", '#/%E3%91%94');
+ }
+
+ function test_validate_path_empty() {
+ $this->assertValidation('http://google.com');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/UnitConverterTest.php b/lib/htmlpurifier/tests/HTMLPurifier/UnitConverterTest.php
new file mode 100644
index 000000000..3dc1e2afa
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/UnitConverterTest.php
@@ -0,0 +1,131 @@
+<?php
+
+class HTMLPurifier_UnitConverterTest extends HTMLPurifier_Harness
+{
+
+ protected function assertConversion($input, $expect, $unit = null, $test_negative = true) {
+ $length = HTMLPurifier_Length::make($input);
+ if ($expect !== false) $expectl = HTMLPurifier_Length::make($expect);
+ else $expectl = false;
+ $to_unit = $unit !== null ? $unit : $expectl->getUnit();
+
+ $converter = new HTMLPurifier_UnitConverter(4, 10);
+ $result = $converter->convert($length, $to_unit);
+ if (!$result || !$expectl) $this->assertIdentical($result, $expectl);
+ else $this->assertIdentical($result->toString(), $expectl->toString());
+
+ $converter = new HTMLPurifier_UnitConverter(4, 10, true);
+ $result = $converter->convert($length, $to_unit);
+ if (!$result || !$expectl) $this->assertIdentical($result, $expectl);
+ else $this->assertIdentical($result->toString(), $expectl->toString(), 'BCMath substitute: %s');
+
+ if ($test_negative) {
+ $this->assertConversion(
+ "-$input",
+ $expect === false ? false : "-$expect",
+ $unit,
+ false
+ );
+ }
+ }
+
+ function testFail() {
+ $this->assertConversion('1in', false, 'foo');
+ $this->assertConversion('1foo', false, 'in');
+ }
+
+ function testZero() {
+ $this->assertConversion('0', '0', 'in', false);
+ $this->assertConversion('-0', '0', 'in', false);
+ $this->assertConversion('0in', '0', 'in', false);
+ $this->assertConversion('-0in', '0', 'in', false);
+ $this->assertConversion('0in', '0', 'pt', false);
+ $this->assertConversion('-0in', '0', 'pt', false);
+ }
+
+ function testEnglish() {
+ $this->assertConversion('1in', '6pc');
+ $this->assertConversion('6pc', '1in');
+
+ $this->assertConversion('1in', '72pt');
+ $this->assertConversion('72pt', '1in');
+
+ $this->assertConversion('1pc', '12pt');
+ $this->assertConversion('12pt', '1pc');
+
+ $this->assertConversion('1pt', '0.01389in');
+ $this->assertConversion('1.000pt', '0.01389in');
+ $this->assertConversion('100000pt', '1389in');
+
+ $this->assertConversion('1in', '96px');
+ $this->assertConversion('96px', '1in');
+ }
+
+ function testMetric() {
+ $this->assertConversion('1cm', '10mm');
+ $this->assertConversion('10mm', '1cm');
+ $this->assertConversion('1mm', '0.1cm');
+ $this->assertConversion('100mm', '10cm');
+ }
+
+ function testEnglishMetric() {
+ $this->assertConversion('2.835pt', '1mm');
+ $this->assertConversion('1mm', '2.835pt');
+ $this->assertConversion('0.3937in', '1cm');
+ }
+
+ function testRoundingMinPrecision() {
+ // One sig-fig, modified to be four, conversion rounds up
+ $this->assertConversion('100pt', '1.389in');
+ $this->assertConversion('1000pt', '13.89in');
+ $this->assertConversion('10000pt', '138.9in');
+ $this->assertConversion('100000pt', '1389in');
+ $this->assertConversion('1000000pt', '13890in');
+ }
+
+ function testRoundingUserPrecision() {
+ // Five sig-figs, conversion rounds down
+ $this->assertConversion('11112000pt', '154330in');
+ $this->assertConversion('1111200pt', '15433in');
+ $this->assertConversion('111120pt', '1543.3in');
+ $this->assertConversion('11112pt', '154.33in');
+ $this->assertConversion('1111.2pt', '15.433in');
+ $this->assertConversion('111.12pt', '1.5433in');
+ $this->assertConversion('11.112pt', '0.15433in');
+ }
+
+ function testRoundingBigNumber() {
+ $this->assertConversion('444400000000000000000000in', '42660000000000000000000000px');
+ }
+
+ protected function assertSigFig($n, $sigfigs) {
+ $converter = new HTMLPurifier_UnitConverter();
+ $result = $converter->getSigFigs($n);
+ $this->assertIdentical($result, $sigfigs);
+ }
+
+ function test_getSigFigs() {
+ $this->assertSigFig('0', 0);
+ $this->assertSigFig('1', 1);
+ $this->assertSigFig('-1', 1);
+ $this->assertSigFig('+1', 1);
+ $this->assertSigFig('01', 1);
+ $this->assertSigFig('001', 1);
+ $this->assertSigFig('12', 2);
+ $this->assertSigFig('012', 2);
+ $this->assertSigFig('10', 1);
+ $this->assertSigFig('10.', 2);
+ $this->assertSigFig('100.', 3);
+ $this->assertSigFig('103', 3);
+ $this->assertSigFig('130', 2);
+ $this->assertSigFig('.1', 1);
+ $this->assertSigFig('0.1', 1);
+ $this->assertSigFig('00.1', 1);
+ $this->assertSigFig('0.01', 1);
+ $this->assertSigFig('0.010', 2);
+ $this->assertSigFig('0.012', 2);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/VarParser/FlexibleTest.php b/lib/htmlpurifier/tests/HTMLPurifier/VarParser/FlexibleTest.php
new file mode 100644
index 000000000..c6149b6b4
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/VarParser/FlexibleTest.php
@@ -0,0 +1,66 @@
+<?php
+
+class HTMLPurifier_VarParser_FlexibleTest extends HTMLPurifier_VarParserHarness
+{
+
+ function testValidate() {
+
+ $this->assertValid('foobar', 'string');
+ $this->assertValid('foobar', 'text');
+ $this->assertValid('FOOBAR', 'istring', 'foobar');
+ $this->assertValid('FOOBAR', 'itext', 'foobar');
+
+ $this->assertValid(34, 'int');
+
+ $this->assertValid(3.34, 'float');
+
+ $this->assertValid(false, 'bool');
+ $this->assertValid(0, 'bool', false);
+ $this->assertValid(1, 'bool', true);
+ $this->assertValid('true', 'bool', true);
+ $this->assertValid('false', 'bool', false);
+ $this->assertValid('1', 'bool', true);
+ $this->assertInvalid(34, 'bool');
+ $this->assertInvalid(null, 'bool');
+
+ $this->assertValid(array('1', '2', '3'), 'list');
+ $this->assertValid('foo,bar, cow', 'list', array('foo', 'bar', 'cow'));
+ $this->assertValid('', 'list', array());
+ $this->assertValid("foo\nbar", 'list', array('foo', 'bar'));
+ $this->assertValid("foo\nbar,baz", 'list', array('foo', 'bar', 'baz'));
+
+ $this->assertValid(array('1' => true, '2' => true), 'lookup');
+ $this->assertValid(array('1', '2'), 'lookup', array('1' => true, '2' => true));
+ $this->assertValid('foo,bar', 'lookup', array('foo' => true, 'bar' => true));
+ $this->assertValid("foo\nbar", 'lookup', array('foo' => true, 'bar' => true));
+ $this->assertValid("foo\nbar,baz", 'lookup', array('foo' => true, 'bar' => true, 'baz' => true));
+ $this->assertValid('', 'lookup', array());
+ $this->assertValid(array(), 'lookup');
+
+ $this->assertValid(array('foo' => 'bar'), 'hash');
+ $this->assertValid(array(1 => 'moo'), 'hash');
+ $this->assertInvalid(array(0 => 'moo'), 'hash');
+ $this->assertValid('', 'hash', array());
+ $this->assertValid('foo:bar,too:two', 'hash', array('foo' => 'bar', 'too' => 'two'));
+ $this->assertValid("foo:bar\ntoo:two,three:free", 'hash', array('foo' => 'bar', 'too' => 'two', 'three' => 'free'));
+ $this->assertValid('foo:bar,too', 'hash', array('foo' => 'bar'));
+ $this->assertValid('foo:bar,', 'hash', array('foo' => 'bar'));
+ $this->assertValid('foo:bar:baz', 'hash', array('foo' => 'bar:baz'));
+
+ $this->assertValid(23, 'mixed');
+
+ }
+
+ function testValidate_withMagicNumbers() {
+ $this->assertValid('foobar', HTMLPurifier_VarParser::STRING);
+ }
+
+ function testValidate_null() {
+ $this->assertIdentical($this->parser->parse(null, 'string', true), null);
+ $this->expectException('HTMLPurifier_VarParserException');
+ $this->parser->parse(null, 'string', false);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/VarParser/NativeTest.php b/lib/htmlpurifier/tests/HTMLPurifier/VarParser/NativeTest.php
new file mode 100644
index 000000000..424dceb75
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/VarParser/NativeTest.php
@@ -0,0 +1,12 @@
+<?php
+
+class HTMLPurifier_VarParser_NativeTest extends HTMLPurifier_VarParserHarness
+{
+
+ public function testValidateSimple() {
+ $this->assertValid('"foo\\\\"', 'string', 'foo\\');
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/lib/htmlpurifier/tests/HTMLPurifier/VarParserHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/VarParserHarness.php
new file mode 100644
index 000000000..0ef23189c
--- /dev/null
+++ b/lib/htmlpurifier/tests/HTMLPurifier/VarParserHarness.php
@@ -0,0 +1,33 @@
+<?php
+
+class HTMLPurifier_VarParserHarness extends UnitTestCase
+{
+
+ protected $parser;
+
+ public function setup() {
+ $class = substr(get_class($this), 0, -4);
+ $this->parser = new $class();
+ }
+
+ function assertValid($var, $type, $ret = null) {
+ $ret = ($ret === null) ? $var : $ret;
+ $this->assertIdentical($this->parser->parse($var, $type), $ret);
+ }
+
+ function assertInvalid($var, $type, $msg = null) {
+ $caught = false;
+ try {
+ $this->parser->parse($var, $type);
+ } catch (HTMLPurifier_VarParserException $e) {
+ $caught = true;
+ if ($msg !== null) $this->assertIdentical($e->getMessage(), $msg);
+ }
+ if (!$caught) {
+ $this->fail('Did not catch expected error');
+ }
+ }
+
+}
+
+// vim: et sw=4 sts=4