pax_global_header00006660000000000000000000000064151473706630014526gustar00rootroot0000000000000052 comment=a8a04134d77f765a166188ef0850369adb6686ab rails-rails-html-sanitizer-a8a0413/000077500000000000000000000000001514737066300172435ustar00rootroot00000000000000rails-rails-html-sanitizer-a8a0413/.github/000077500000000000000000000000001514737066300206035ustar00rootroot00000000000000rails-rails-html-sanitizer-a8a0413/.github/workflows/000077500000000000000000000000001514737066300226405ustar00rootroot00000000000000rails-rails-html-sanitizer-a8a0413/.github/workflows/ci.yml000066400000000000000000000027721514737066300237660ustar00rootroot00000000000000name: ci concurrency: group: "${{github.workflow}}-${{github.ref}}" cancel-in-progress: true on: workflow_dispatch: push: branches: - main - v*.*.x tags: - v*.*.* pull_request: types: [opened, synchronize] branches: - '*' jobs: rubocop: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: rm Gemfile.lock - uses: ruby/setup-ruby@v1 with: ruby-version: "4.0" bundler-cache: true - run: bundle exec rubocop cruby: strategy: fail-fast: false matrix: ruby: ["2.7", "3.0", "3.1", "3.2", "3.3", "3.4", "4.0", "ruby-head", "truffleruby-head", "jruby-9.4", "jruby-10.0", "jruby-head"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: rm Gemfile.lock - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} bundler-cache: true - run: bundle exec rake cruby-nokogiri-system-libraries: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: rm Gemfile.lock - uses: ruby/setup-ruby@v1 with: ruby-version: "4.0" - name: Install nokogiri with system libraries run: | sudo apt install pkg-config libxml2-dev libxslt-dev bundle config set force_ruby_platform true bundle config build.nokogiri --enable-system-libraries bundle install bundle exec nokogiri -v - run: bundle exec rake rails-rails-html-sanitizer-a8a0413/.gitignore000066400000000000000000000002151514737066300212310ustar00rootroot00000000000000*.gem *.rbc .bundle .config .yardoc InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp rails-rails-html-sanitizer-a8a0413/.rdoc_options000066400000000000000000000007021514737066300217450ustar00rootroot00000000000000--- encoding: UTF-8 static_path: [] rdoc_include: [] page_dir: charset: UTF-8 exclude: - "~\\z" - "\\.orig\\z" - "\\.rej\\z" - "\\.bak\\z" - "\\.gemspec\\z" - "issues" - "Gemfile*" - "Rakefile" hyperlink_all: false line_numbers: false locale: locale_dir: locale locale_name: main_page: "README.md" markup: rdoc output_decoration: true show_hash: false skip_tests: true tab_width: 8 template_stylesheets: [] title: visibility: :protected webcvs: rails-rails-html-sanitizer-a8a0413/.rubocop.yml000066400000000000000000000153121514737066300215170ustar00rootroot00000000000000require: - rubocop-minitest - rubocop-packaging - rubocop-performance - rubocop-rails AllCops: TargetRubyVersion: 2.7 # RuboCop has a bunch of cops enabled by default. This setting tells RuboCop # to ignore them, so only the ones explicitly set in this file are enabled. DisabledByDefault: true SuggestExtensions: false Exclude: - '**/tmp/**/*' - '**/templates/**/*' - '**/vendor/**/*' - 'actionpack/lib/action_dispatch/journey/parser.rb' - 'actionmailbox/test/dummy/**/*' - 'actiontext/test/dummy/**/*' - '**/node_modules/**/*' Performance: Exclude: - '**/test/**/*' Rails/IndexBy: Enabled: true Rails/IndexWith: Enabled: true # Prefer &&/|| over and/or. Style/AndOr: Enabled: true # Align `when` with `case`. Layout/CaseIndentation: Enabled: true Layout/ClosingHeredocIndentation: Enabled: true Layout/ClosingParenthesisIndentation: Enabled: true # Align comments with method definitions. Layout/CommentIndentation: Enabled: true Layout/ElseAlignment: Enabled: true # Align `end` with the matching keyword or starting expression except for # assignments, where it should be aligned with the LHS. Layout/EndAlignment: Enabled: true EnforcedStyleAlignWith: variable AutoCorrect: true Layout/EndOfLine: Enabled: true Layout/EmptyLineAfterMagicComment: Enabled: true Layout/EmptyLinesAroundAccessModifier: Enabled: true EnforcedStyle: only_before Layout/EmptyLinesAroundBlockBody: Enabled: true # In a regular class definition, no empty lines around the body. Layout/EmptyLinesAroundClassBody: Enabled: true # In a regular method definition, no empty lines around the body. Layout/EmptyLinesAroundMethodBody: Enabled: true # In a regular module definition, no empty lines around the body. Layout/EmptyLinesAroundModuleBody: Enabled: true # Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }. Style/HashSyntax: Enabled: true # Method definitions after `private` or `protected` isolated calls need one # extra level of indentation. Layout/IndentationConsistency: Enabled: true EnforcedStyle: indented_internal_methods # Two spaces, no tabs (for indentation). Layout/IndentationWidth: Enabled: true Layout/LeadingCommentSpace: Enabled: true Layout/SpaceAfterColon: Enabled: true Layout/SpaceAfterComma: Enabled: true Layout/SpaceAfterSemicolon: Enabled: true Layout/SpaceAroundEqualsInParameterDefault: Enabled: true Layout/SpaceAroundKeyword: Enabled: true Layout/SpaceAroundOperators: Enabled: true Layout/SpaceBeforeComma: Enabled: true Layout/SpaceBeforeComment: Enabled: true Layout/SpaceBeforeFirstArg: Enabled: true Style/DefWithParentheses: Enabled: true # Defining a method with parameters needs parentheses. Style/MethodDefParentheses: Enabled: true Style/ExplicitBlockArgument: Enabled: true Style/FrozenStringLiteralComment: Enabled: true EnforcedStyle: always Exclude: - 'actionview/test/**/*.builder' - 'actionview/test/**/*.ruby' - 'actionpack/test/**/*.builder' - 'actionpack/test/**/*.ruby' - 'activestorage/db/migrate/**/*.rb' - 'activestorage/db/update_migrate/**/*.rb' - 'actionmailbox/db/migrate/**/*.rb' - 'actiontext/db/migrate/**/*.rb' Style/MapToHash: Enabled: true Style/RedundantFreeze: Enabled: true # Use `foo {}` not `foo{}`. Layout/SpaceBeforeBlockBraces: Enabled: true # Use `foo { bar }` not `foo {bar}`. Layout/SpaceInsideBlockBraces: Enabled: true EnforcedStyleForEmptyBraces: space # Use `{ a: 1 }` not `{a:1}`. Layout/SpaceInsideHashLiteralBraces: Enabled: true Layout/SpaceInsideParens: Enabled: true # Check quotes usage according to lint rule below. Style/StringLiterals: Enabled: true EnforcedStyle: double_quotes # Detect hard tabs, no hard tabs. Layout/IndentationStyle: Enabled: true # Empty lines should not have any spaces. Layout/TrailingEmptyLines: Enabled: true # No trailing whitespace. Layout/TrailingWhitespace: Enabled: true # Use quotes for string literals when they are enough. Style/RedundantPercentQ: Enabled: true Lint/AmbiguousOperator: Enabled: true Lint/AmbiguousRegexpLiteral: Enabled: true Lint/DuplicateRequire: Enabled: true Lint/DuplicateMagicComment: Enabled: true Lint/DuplicateMethods: Enabled: true Lint/ErbNewArguments: Enabled: true Lint/EnsureReturn: Enabled: true Lint/MissingCopEnableDirective: Enabled: true # Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg. Lint/RequireParentheses: Enabled: true Lint/RedundantCopDisableDirective: Enabled: true Lint/RedundantCopEnableDirective: Enabled: true Lint/RedundantStringCoercion: Enabled: true Lint/RedundantSafeNavigation: Enabled: true Lint/UriEscapeUnescape: Enabled: true Lint/UselessAssignment: Enabled: true Lint/DeprecatedClassMethods: Enabled: true Lint/InterpolationCheck: Enabled: true Exclude: - '**/test/**/*' Lint/SafeNavigationChain: Enabled: true Style/EvalWithLocation: Enabled: true Exclude: - '**/test/**/*' Style/ParenthesesAroundCondition: Enabled: true Style/HashTransformKeys: Enabled: true Style/HashTransformValues: Enabled: true Style/RedundantBegin: Enabled: true Style/RedundantReturn: Enabled: true AllowMultipleReturnValues: true Style/RedundantRegexpEscape: Enabled: true Style/Semicolon: Enabled: true AllowAsExpressionSeparator: true # Prefer Foo.method over Foo::method Style/ColonMethodCall: Enabled: true Style/TrivialAccessors: Enabled: true # Prefer a = b || c over a = b ? b : c Style/RedundantCondition: Enabled: true Style/RedundantDoubleSplatHashBraces: Enabled: true Style/ArrayIntersect: Enabled: true Performance/BindCall: Enabled: true Performance/FlatMap: Enabled: true Performance/MapCompact: Enabled: true Performance/SelectMap: Enabled: true Performance/RedundantMerge: Enabled: true Performance/StartWith: Enabled: true Performance/EndWith: Enabled: true Performance/RegexpMatch: Enabled: true Performance/ReverseEach: Enabled: true Performance/StringReplacement: Enabled: true Performance/DeletePrefix: Enabled: true Performance/DeleteSuffix: Enabled: true Performance/OpenStruct: Enabled: true Performance/InefficientHashSearch: Enabled: true Performance/ConstantRegexp: Enabled: true Performance/RedundantStringChars: Enabled: true Performance/StringInclude: Enabled: true Minitest/AssertPredicate: Enabled: true Minitest/AssertRaisesWithRegexpArgument: Enabled: true Minitest/AssertWithExpectedArgument: Enabled: true Minitest/LiteralAsActualArgument: Enabled: true Minitest/NonExecutableTestMethod: Enabled: true Minitest/SkipEnsure: Enabled: true Minitest/UnreachableAssertion: Enabled: true Minitest/NoAssertions: Enabled: true rails-rails-html-sanitizer-a8a0413/CHANGELOG.md000066400000000000000000000174211514737066300210610ustar00rootroot00000000000000## v1.7.0 / 2026-02-24 * Add `Rails::HTML::Sanitizer.allowed_uri?` which delegates to `Loofah::HTML5::Scrub.allowed_uri?`, allowing the Rails framework to check URI safety without a direct dependency on Loofah. The minimum Loofah dependency is now `~> 2.25`. *Mike Dalessio* ## v1.6.2 / 2024-12-12 * `PermitScrubber` fully supports frozen "allowed tags". v1.6.1 introduced safety checks that may remove unsafe tags from the allowed list, which introduced a regression for applications passing a frozen array of allowed tags. Tags and attributes are now properly copied when they are passed to the scrubber. Fixes #195. *Mike Dalessio* ## 1.6.1 / 2024-12-02 This is a performance and security release which addresses several possible XSS vulnerabilities. * The dependency on Nokogiri is updated to v1.15.7 or >=1.16.8. This change addresses CVE-2024-53985 (GHSA-w8gc-x259-rc7x). *Mike Dalessio* * Disallowed tags will be pruned when they appear in foreign content (i.e. SVG or MathML content), regardless of the `prune:` option value. Previously, disallowed tags were "stripped" unless the gem was configured with the `prune: true` option. The CVEs addressed by this change are: - CVE-2024-53986 (GHSA-638j-pmjw-jq48) - CVE-2024-53987 (GHSA-2x5m-9ch4-qgrr) *Mike Dalessio* * The tags "noscript", "mglyph", and "malignmark" will not be allowed, even if explicitly added to the allowlist. If applications try to allow any of these tags, a warning is emitted and the tags are removed from the allow-list. The CVEs addressed by this change are: - CVE-2024-53988 (GHSA-cfjx-w229-hgx5) - CVE-2024-53989 (GHSA-rxv5-gxqc-xx8g) Please note that we _may_ restore support for allowing "noscript" in a future release. We do not expect to ever allow "mglyph" or "malignmark", though, especially since browser support is minimal for these tags. *Mike Dalessio* * Improve performance by eliminating needless operations on attributes that are being removed. #188 *Mike Dalessio* ## 1.6.0 / 2023-05-26 * Dependencies have been updated: - Loofah `~>2.21` and Nokogiri `~>1.14` for HTML5 parser support - As a result, required Ruby version is now `>= 2.7.0` Security updates will continue to be made on the `1.5.x` release branch as long as Rails 6.1 (which supports Ruby 2.5) is still in security support. *Mike Dalessio* * HTML5 standards-compliant sanitizers are now available on platforms supported by Nokogiri::HTML5. These are available as: - `Rails::HTML5::FullSanitizer` - `Rails::HTML5::LinkSanitizer` - `Rails::HTML5::SafeListSanitizer` And a new "vendor" is provided at `Rails::HTML5::Sanitizer` that can be used in a future version of Rails. Note that for symmetry `Rails::HTML4::Sanitizer` is also added, though its behavior is identical to the vendor class methods on `Rails::HTML::Sanitizer`. Users may call `Rails::HTML::Sanitizer.best_supported_vendor` to get back the HTML5 vendor if it's supported, else the legacy HTML4 vendor. *Mike Dalessio* * Module namespaces have changed, but backwards compatibility is provided by aliases. The library defines three additional modules: - `Rails::HTML` for general functionality (replacing `Rails::Html`) - `Rails::HTML4` containing sanitizers that parse content as HTML4 - `Rails::HTML5` containing sanitizers that parse content as HTML5 The following aliases are maintained for backwards compatibility: - `Rails::Html` points to `Rails::HTML` - `Rails::HTML::FullSanitizer` points to `Rails::HTML4::FullSanitizer` - `Rails::HTML::LinkSanitizer` points to `Rails::HTML4::LinkSanitizer` - `Rails::HTML::SafeListSanitizer` points to `Rails::HTML4::SafeListSanitizer` *Mike Dalessio* * `LinkSanitizer` always returns UTF-8 encoded strings. `SafeListSanitizer` and `FullSanitizer` already ensured this encoding. *Mike Dalessio* * `SafeListSanitizer` allows `time` tag and `lang` attribute by default. *Mike Dalessio* * The constant `Rails::Html::XPATHS_TO_REMOVE` has been removed. It's not necessary with the existing sanitizers, and should have been a private constant all along anyway. *Mike Dalessio* ## 1.5.0 / 2023-01-20 * `SafeListSanitizer`, `PermitScrubber`, and `TargetScrubber` now all support pruning of unsafe tags. By default, unsafe tags are still stripped, but this behavior can be changed to prune the element and its children from the document by passing `prune: true` to any of these classes' constructors. *seyerian* ## 1.4.4 / 2022-12-13 * Address inefficient regular expression complexity with certain configurations of Rails::Html::Sanitizer. Fixes CVE-2022-23517. See [GHSA-5x79-w82f-gw8w](https://github.com/rails/rails-html-sanitizer/security/advisories/GHSA-5x79-w82f-gw8w) for more information. *Mike Dalessio* * Address improper sanitization of data URIs. Fixes CVE-2022-23518 and #135. See [GHSA-mcvf-2q2m-x72m](https://github.com/rails/rails-html-sanitizer/security/advisories/GHSA-mcvf-2q2m-x72m) for more information. *Mike Dalessio* * Address possible XSS vulnerability with certain configurations of Rails::Html::Sanitizer. Fixes CVE-2022-23520. See [GHSA-rrfc-7g8p-99q8](https://github.com/rails/rails-html-sanitizer/security/advisories/GHSA-rrfc-7g8p-99q8) for more information. *Mike Dalessio* * Address possible XSS vulnerability with certain configurations of Rails::Html::Sanitizer. Fixes CVE-2022-23519. See [GHSA-9h9g-93gc-623h](https://github.com/rails/rails-html-sanitizer/security/advisories/GHSA-9h9g-93gc-623h) for more information. *Mike Dalessio* ## 1.4.3 / 2022-06-09 * Address a possible XSS vulnerability with certain configurations of Rails::Html::Sanitizer. Prevent the combination of `select` and `style` as allowed tags in SafeListSanitizer. Fixes CVE-2022-32209 *Mike Dalessio* ## 1.4.2 / 2021-08-23 * Slightly improve performance. Assuming elements are more common than comments, make one less method call per node. *Mike Dalessio* ## 1.4.1 / 2021-08-18 * Fix regression in v1.4.0 that did not pass comment nodes to the scrubber. Some scrubbers will want to override the default behavior and allow comments, but v1.4.0 only passed through elements to the scrubber's `keep_node?` method. This change once again allows the scrubber to make the decision on comment nodes, but still skips other non-elements like processing instructions (see #115). *Mike Dalessio* ## 1.4.0 / 2021-08-18 * Processing Instructions are no longer allowed by Rails::Html::PermitScrubber Previously, a PI with a name (or "target") matching an allowed tag name was not scrubbed. There are no known security issues associated with these PIs, but similar to comments it's preferred to omit these nodes when possible from sanitized output. Fixes #115. *Mike Dalessio* ## 1.3.0 * Address deprecations in Loofah 2.3.0. *Josh Goodall* ## 1.2.0 * Remove needless `white_list_sanitizer` deprecation. By deprecating this, we were forcing Rails 5.2 to be updated or spew deprecations that users could do nothing about. That's pointless and I'm sorry for adding that! Now there's no deprecation warning and Rails 5.2 works out of the box, while Rails 6 can use the updated naming. *Kasper Timm Hansen* ## 1.1.0 * Add `safe_list_sanitizer` and deprecate `white_list_sanitizer` to be removed in 1.2.0. https://github.com/rails/rails-html-sanitizer/pull/87 *Juanito Fatas* * Remove `href` from LinkScrubber's `tags` as it's not an element. https://github.com/rails/rails-html-sanitizer/pull/92 *Juanito Fatas* * Explain that we don't need to bump Loofah here if there's CVEs. https://github.com/rails/rails-html-sanitizer/commit/d4d823c617fdd0064956047f7fbf23fff305a69b *Kasper Timm Hansen* ## 1.0.1 * Added support for Rails 4.2.0.beta2 and above ## 1.0.0 * First release. rails-rails-html-sanitizer-a8a0413/CONTRIBUTING.md000066400000000000000000000067221514737066300215030ustar00rootroot00000000000000Contributing to Rails Html Sanitizers ===================== [![Build Status](https://github.com/rails/rails-html-sanitizer/actions/workflows/ci.yml/badge.svg)](https://github.com/rails/rails-html-sanitizer/actions/workflows/ci.yml) Rails Html Sanitizers is work of [many contributors](https://github.com/rails/rails-html-sanitizer/graphs/contributors). You're encouraged to submit [pull requests](https://github.com/rails/rails-html-sanitizer/pulls), [propose features and discuss issues](https://github.com/rails/rails-html-sanitizer/issues). ### How to submit a pull request #### Fork the Project Fork the [project on Github](https://github.com/rails/rails-html-sanitizer) and check out your copy. ``` git clone https://github.com/contributor/rails-html-sanitizer.git cd rails-html-sanitizer git remote add upstream https://github.com/rails/rails-html-sanitizer.git ``` #### Create a Topic Branch Make sure your fork is up-to-date and create a topic branch for your feature or bug fix. ``` git checkout main git pull upstream main git checkout -b my-feature-branch ``` #### Bundle Install and Test Ensure that you can build the project and run tests. ``` bundle install bundle exec rake test ``` #### Write Tests Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. Add to [test](test). We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix. #### Write Code Implement your feature or bug fix. Make sure that `bundle exec rake test` completes without errors. #### Write Documentation Document any external behavior in the [README](README.md). #### Commit Changes Make sure git knows your name and email address: ``` git config --global user.name "Your Name" git config --global user.email "contributor@example.com" ``` Writing good commit logs is important. A commit log should describe what changed and why. ``` git add ... git commit ``` #### Push ``` git push origin my-feature-branch ``` #### Make a Pull Request Go to https://github.com/contributor/rails-html-sanitizer and select your feature branch. Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days. #### Rebase If you've been working on a change for a while, rebase with upstream/main. ``` git fetch upstream git rebase upstream/main git push origin my-feature-branch -f ``` #### Check on Your Pull Request Go back to your pull request after a few minutes and see whether it passed muster with CI. Everything should look green, otherwise fix issues and amend your commit as described above. #### Be Patient It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang on there! #### Thank You Please do know that we really appreciate and value your time and work. We love you, really. ### How to cut a release A quick checklist: - [ ] make sure CI is green! https://github.com/rails/rails-html-sanitizer/actions/workflows/ci.yml - [ ] update `CHANGELOG.md` and `lib/rails/html/sanitizer/version.rb` - [ ] run `bundle exec rake build` - [ ] create a git tag - [ ] `git push && git push --tags` - [ ] `gem push pkg/*.gem` - [ ] create a release at https://github.com/rails/rails-html-sanitizer/releases - if security-related, - [ ] publish the CVE - [ ] post to https://discuss.rubyonrails.org/c/security-announcements - [ ] submit a PR to https://github.com/rubysec/ruby-advisory-db rails-rails-html-sanitizer-a8a0413/Gemfile000066400000000000000000000005211514737066300205340ustar00rootroot00000000000000# frozen_string_literal: true source "https://rubygems.org" gemspec gem "rake" gem "minitest", "< 6" group :rubocop do gem "rubocop", ">= 1.25.1", require: false gem "rubocop-minitest", require: false gem "rubocop-packaging", require: false gem "rubocop-performance", require: false gem "rubocop-rails", require: false end rails-rails-html-sanitizer-a8a0413/Gemfile.lock000066400000000000000000000161621514737066300214730ustar00rootroot00000000000000PATH remote: . specs: rails-html-sanitizer (1.7.0.dev) loofah (~> 2.25) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) GEM remote: https://rubygems.org/ specs: activesupport (8.1.2) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) json logger (>= 1.4.2) minitest (>= 5.1) securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) uri (>= 0.13.1) ast (2.4.3) base64 (0.3.0) bigdecimal (4.0.1) concurrent-ruby (1.3.6) connection_pool (3.0.2) crass (1.0.6) drb (2.2.3) i18n (1.14.8) concurrent-ruby (~> 1.0) json (2.18.1) language_server-protocol (3.17.0.5) lint_roller (1.1.0) logger (1.7.0) loofah (2.25.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) minitest (5.27.0) nokogiri (1.19.1-aarch64-linux-gnu) racc (~> 1.4) nokogiri (1.19.1-aarch64-linux-musl) racc (~> 1.4) nokogiri (1.19.1-arm-linux-gnu) racc (~> 1.4) nokogiri (1.19.1-arm-linux-musl) racc (~> 1.4) nokogiri (1.19.1-arm64-darwin) racc (~> 1.4) nokogiri (1.19.1-x86_64-darwin) racc (~> 1.4) nokogiri (1.19.1-x86_64-linux-gnu) racc (~> 1.4) nokogiri (1.19.1-x86_64-linux-musl) racc (~> 1.4) parallel (1.27.0) parser (3.3.10.2) ast (~> 2.4.1) racc prism (1.9.0) racc (1.8.1) rack (3.2.5) rainbow (3.1.1) rake (13.3.1) regexp_parser (2.11.3) rubocop (1.84.2) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) rubocop-ast (>= 1.49.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) rubocop-ast (1.49.0) parser (>= 3.3.7.2) prism (~> 1.7) rubocop-minitest (0.39.1) lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.38.0, < 2.0) rubocop-packaging (0.6.0) lint_roller (~> 1.1.0) rubocop (>= 1.72.1, < 2.0) rubocop-performance (1.26.1) lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.47.1, < 2.0) rubocop-rails (2.34.3) activesupport (>= 4.2.0) lint_roller (~> 1.1) rack (>= 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.44.0, < 2.0) ruby-progressbar (1.13.0) securerandom (0.4.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (3.2.0) unicode-emoji (~> 4.1) unicode-emoji (4.2.0) uri (1.1.1) PLATFORMS aarch64-linux-gnu aarch64-linux-musl arm-linux-gnu arm-linux-musl arm64-darwin x86_64-darwin x86_64-linux-gnu x86_64-linux-musl DEPENDENCIES minitest (< 6) rails-html-sanitizer! rake rubocop (>= 1.25.1) rubocop-minitest rubocop-packaging rubocop-performance rubocop-rails CHECKSUMS activesupport (8.1.2) sha256=88842578ccd0d40f658289b0e8c842acfe9af751afee2e0744a7873f50b6fdae ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383 base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7 concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373 i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5 json (2.18.1) sha256=fe112755501b8d0466b5ada6cf50c8c3f41e897fa128ac5d263ec09eedc9f986 language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87 logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 loofah (2.25.0) sha256=df5ed7ac3bac6a4ec802df3877ee5cc86d027299f8952e6243b3dac446b060e6 minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5 nokogiri (1.19.1-aarch64-linux-gnu) sha256=cfdb0eafd9a554a88f12ebcc688d2b9005f9fce42b00b970e3dc199587b27f32 nokogiri (1.19.1-aarch64-linux-musl) sha256=1e2150ab43c3b373aba76cd1190af7b9e92103564063e48c474f7600923620b5 nokogiri (1.19.1-arm-linux-gnu) sha256=0a39ed59abe3bf279fab9dd4c6db6fe8af01af0608f6e1f08b8ffa4e5d407fa3 nokogiri (1.19.1-arm-linux-musl) sha256=3a18e559ee499b064aac6562d98daab3d39ba6cbb4074a1542781b2f556db47d nokogiri (1.19.1-arm64-darwin) sha256=dfe2d337e6700eac47290407c289d56bcf85805d128c1b5a6434ddb79731cb9e nokogiri (1.19.1-x86_64-darwin) sha256=7093896778cc03efb74b85f915a775862730e887f2e58d6921e3fa3d981e68bf nokogiri (1.19.1-x86_64-linux-gnu) sha256=1a4902842a186b4f901078e692d12257678e6133858d0566152fe29cdb98456a nokogiri (1.19.1-x86_64-linux-musl) sha256=4267f38ad4fc7e52a2e7ee28ed494e8f9d8eb4f4b3320901d55981c7b995fc23 parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130 parser (3.3.10.2) sha256=6f60c84aa4bdcedb6d1a2434b738fe8a8136807b6adc8f7f53b97da9bc4e9357 prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85 racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f rack (3.2.5) sha256=4cbd0974c0b79f7a139b4812004a62e4c60b145cba76422e288ee670601ed6d3 rails-html-sanitizer (1.7.0.dev) rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4 rubocop (1.84.2) sha256=5692cea54168f3dc8cb79a6fe95c5424b7ea893c707ad7a4307b0585e88dbf5f rubocop-ast (1.49.0) sha256=49c3676d3123a0923d333e20c6c2dbaaae2d2287b475273fddee0c61da9f71fd rubocop-minitest (0.39.1) sha256=998398d6da4026d297f0f9bf709a1eac5f2b6947c24431f94af08138510cf7ed rubocop-packaging (0.6.0) sha256=fb92bd0fb48e6f8cdb1648d2249b0cd51c2497dcc87340132d22f01edbf558a7 rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834 rubocop-rails (2.34.3) sha256=10d37989024865ecda8199f311f3faca990143fbac967de943f88aca11eb9ad2 ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1 tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42 unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6 BUNDLED WITH 4.0.6 rails-rails-html-sanitizer-a8a0413/MIT-LICENSE000066400000000000000000000021421514737066300206760ustar00rootroot00000000000000Copyright (c) 2013-2023 Rafael Mendonça França, Kasper Timm Hansen, Mike Dalessio MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rails-rails-html-sanitizer-a8a0413/README.md000066400000000000000000000225441514737066300205310ustar00rootroot00000000000000# Rails HTML Sanitizers This gem is responsible for sanitizing HTML fragments in Rails applications. Specifically, this is the set of sanitizers used to implement the Action View `SanitizerHelper` methods `sanitize`, `sanitize_css`, `strip_tags` and `strip_links`. Rails HTML Sanitizer is only intended to be used with Rails applications. If you need similar functionality but aren't using Rails, consider using the underlying sanitization library [Loofah](https://github.com/flavorjones/loofah) directly. ## Usage ### Sanitizers All sanitizers respond to `sanitize`, and are available in variants that use either HTML4 or HTML5 parsing, under the `Rails::HTML4` and `Rails::HTML5` namespaces, respectively. NOTE: The HTML5 sanitizers are not supported on JRuby. Users may programmatically check for support by calling `Rails::HTML::Sanitizer.html5_support?`. #### FullSanitizer ```ruby full_sanitizer = Rails::HTML5::FullSanitizer.new full_sanitizer.sanitize("Bold no more! See more here...") # => Bold no more! See more here... ``` or, if you insist on parsing the content as HTML4: ```ruby full_sanitizer = Rails::HTML4::FullSanitizer.new full_sanitizer.sanitize("Bold no more! See more here...") # => Bold no more! See more here... ``` #### LinkSanitizer ```ruby link_sanitizer = Rails::HTML5::LinkSanitizer.new link_sanitizer.sanitize('Only the link text will be kept.') # => Only the link text will be kept. ``` or, if you insist on parsing the content as HTML4: ```ruby link_sanitizer = Rails::HTML4::LinkSanitizer.new link_sanitizer.sanitize('Only the link text will be kept.') # => Only the link text will be kept. ``` #### SafeListSanitizer This sanitizer is also available as an HTML4 variant, but for simplicity we'll document only the HTML5 variant below. ```ruby safe_list_sanitizer = Rails::HTML5::SafeListSanitizer.new # sanitize via an extensive safe list of allowed elements safe_list_sanitizer.sanitize(@article.body) # sanitize only the supplied tags and attributes safe_list_sanitizer.sanitize(@article.body, tags: %w(table tr td), attributes: %w(id class style)) # sanitize via a custom scrubber safe_list_sanitizer.sanitize(@article.body, scrubber: ArticleScrubber.new) # prune nodes from the tree instead of stripping tags and leaving inner content safe_list_sanitizer = Rails::HTML5::SafeListSanitizer.new(prune: true) # the sanitizer can also sanitize css safe_list_sanitizer.sanitize_css('background-color: #000;') ``` ### Scrubbers Scrubbers are objects responsible for removing nodes or attributes you don't want in your HTML document. This gem includes two scrubbers `Rails::HTML::PermitScrubber` and `Rails::HTML::TargetScrubber`. #### `Rails::HTML::PermitScrubber` This scrubber allows you to permit only the tags and attributes you want. ```ruby scrubber = Rails::HTML::PermitScrubber.new scrubber.tags = ['a'] html_fragment = Loofah.fragment('') html_fragment.scrub!(scrubber) html_fragment.to_s # => "" ``` By default, inner content is left, but it can be removed as well. ```ruby scrubber = Rails::HTML::PermitScrubber.new scrubber.tags = ['a'] html_fragment = Loofah.fragment('text') html_fragment.scrub!(scrubber) html_fragment.to_s # => "text" scrubber = Rails::HTML::PermitScrubber.new(prune: true) scrubber.tags = ['a'] html_fragment = Loofah.fragment('text') html_fragment.scrub!(scrubber) html_fragment.to_s # => "" ``` #### `Rails::HTML::TargetScrubber` Where `PermitScrubber` picks out tags and attributes to permit in sanitization, `Rails::HTML::TargetScrubber` targets them for removal. See https://github.com/flavorjones/loofah/blob/main/lib/loofah/html5/safelist.rb for the tag list. **Note:** by default, it will scrub anything that is not part of the permitted tags from loofah `HTML5::Scrub.allowed_element?`. ```ruby scrubber = Rails::HTML::TargetScrubber.new scrubber.tags = ['img'] html_fragment = Loofah.fragment('') html_fragment.scrub!(scrubber) html_fragment.to_s # => "" ``` Similarly to `PermitScrubber`, nodes can be fully pruned. ```ruby scrubber = Rails::HTML::TargetScrubber.new scrubber.tags = ['span'] html_fragment = Loofah.fragment('text') html_fragment.scrub!(scrubber) html_fragment.to_s # => "text" scrubber = Rails::HTML::TargetScrubber.new(prune: true) scrubber.tags = ['span'] html_fragment = Loofah.fragment('text') html_fragment.scrub!(scrubber) html_fragment.to_s # => "" ``` #### Custom Scrubbers You can also create custom scrubbers in your application if you want to. ```ruby class CommentScrubber < Rails::HTML::PermitScrubber def initialize super self.tags = %w( form script comment blockquote ) self.attributes = %w( style ) end def skip_node?(node) node.text? end end ``` See `Rails::HTML::PermitScrubber` documentation to learn more about which methods can be overridden. #### Custom Scrubber in a Rails app Using the `CommentScrubber` from above, you can use this in a Rails view like so: ```ruby <%= sanitize @comment, scrubber: CommentScrubber.new %> ``` ### A note on HTML entities __Rails HTML sanitizers are intended to be used by the view layer, at page-render time. They are *not* intended to sanitize persisted strings that will be sanitized *again* at page-render time.__ Proper HTML sanitization will replace some characters with HTML entities. For example, text containing a `<` character will be updated to contain `<` to ensure that the markup is well-formed. This is important to keep in mind because __HTML entities will render improperly if they are sanitized twice.__ #### A concrete example showing the problem that can arise Imagine the user is asked to enter their employer's name, which will appear on their public profile page. Then imagine they enter `JPMorgan Chase & Co.`. If you sanitize this before persisting it in the database, the stored string will be `JPMorgan Chase & Co.` When the page is rendered, if this string is sanitized a second time by the view layer, the HTML will contain `JPMorgan Chase &amp; Co.` which will render as "JPMorgan Chase &amp; Co.". Another problem that can arise is rendering the sanitized string in a non-HTML context (for example, if it ends up being part of an SMS message). In this case, it may contain inappropriate HTML entities. #### Suggested alternatives You might simply choose to persist the untrusted string as-is (the raw input), and then ensure that the string will be properly sanitized by the view layer. That raw string, if rendered in an non-HTML context (like SMS), must also be sanitized by a method appropriate for that context. You may wish to look into using [Loofah](https://github.com/flavorjones/loofah) or [Sanitize](https://github.com/rgrove/sanitize) to customize how this sanitization works, including omitting HTML entities in the final string. If you really want to sanitize the string that's stored in your database, you may wish to look into [Loofah::ActiveRecord](https://github.com/flavorjones/loofah-activerecord) rather than use the Rails HTML sanitizers. ### A note on module names In versions < 1.6, the only module defined by this library was `Rails::Html`. Starting in 1.6, we define three additional modules: - `Rails::HTML` for general functionality (replacing `Rails::Html`) - `Rails::HTML4` containing sanitizers that parse content as HTML4 - `Rails::HTML5` containing sanitizers that parse content as HTML5 (if supported) The following aliases are maintained for backwards compatibility: - `Rails::Html` points to `Rails::HTML` - `Rails::HTML::FullSanitizer` points to `Rails::HTML4::FullSanitizer` - `Rails::HTML::LinkSanitizer` points to `Rails::HTML4::LinkSanitizer` - `Rails::HTML::SafeListSanitizer` points to `Rails::HTML4::SafeListSanitizer` ## Installation Add this line to your application's Gemfile: gem 'rails-html-sanitizer' And then execute: $ bundle Or install it yourself as: $ gem install rails-html-sanitizer ## Support matrix | branch | ruby support | actively maintained | security support | |--------|--------------|---------------------|----------------------------------------| | 1.6.x | >= 2.7 | yes | yes | | 1.5.x | >= 2.5 | no | while Rails 6.1 is in security support | | 1.4.x | >= 1.8.7 | no | no | ## Read more Loofah is what underlies the sanitizers and scrubbers of rails-html-sanitizer. - [Loofah and Loofah Scrubbers](https://github.com/flavorjones/loofah) The `node` argument passed to some methods in a custom scrubber is an instance of `Nokogiri::XML::Node`. - [`Nokogiri::XML::Node`](https://nokogiri.org/rdoc/Nokogiri/XML/Node.html) - [Nokogiri](http://nokogiri.org) ## Contributing to Rails HTML Sanitizers Rails HTML Sanitizers is work of many contributors. You're encouraged to submit pull requests, propose features and discuss issues. See [CONTRIBUTING](CONTRIBUTING.md). ### Security reports Trying to report a possible security vulnerability in this project? Please check out the [Rails project's security policy](https://rubyonrails.org/security) for instructions. ## License Rails HTML Sanitizers is released under the [MIT License](MIT-LICENSE). rails-rails-html-sanitizer-a8a0413/Rakefile000066400000000000000000000003161514737066300207100ustar00rootroot00000000000000# frozen_string_literal: true require "bundler/gem_tasks" require "rake/testtask" task default: :test Rake::TestTask.new do |t| t.pattern = "test/**/*_test.rb" t.warning = true t.verbose = true end rails-rails-html-sanitizer-a8a0413/lib/000077500000000000000000000000001514737066300200115ustar00rootroot00000000000000rails-rails-html-sanitizer-a8a0413/lib/rails-html-sanitizer.rb000066400000000000000000000035651514737066300244310ustar00rootroot00000000000000# frozen_string_literal: true require_relative "rails/html/sanitizer/version" require "loofah" require_relative "rails/html/scrubbers" require_relative "rails/html/sanitizer" module Rails Html = HTML # :nodoc: end module ActionView module Helpers module SanitizeHelper module ClassMethods # Replaces the allowed tags for the +sanitize+ helper. # # class Application < Rails::Application # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' # end # def sanitized_allowed_tags=(tags) sanitizer_vendor.safe_list_sanitizer.allowed_tags = tags end # Replaces the allowed HTML attributes for the +sanitize+ helper. # # class Application < Rails::Application # config.action_view.sanitized_allowed_attributes = ['onclick', 'longdesc'] # end # def sanitized_allowed_attributes=(attributes) sanitizer_vendor.safe_list_sanitizer.allowed_attributes = attributes end [:protocol_separator, :uri_attributes, :bad_tags, :allowed_css_properties, :allowed_css_keywords, :shorthand_css_properties, :allowed_protocols].each do |meth| meth_name = "sanitized_#{meth}" define_method(meth_name) { deprecate_option(meth_name) } define_method("#{meth_name}=") { |_| deprecate_option("#{meth_name}=") } end private def deprecate_option(name) ActiveSupport::Deprecation.warn "The #{name} option is deprecated " \ "and has no effect. Until Rails 5 the old behavior can still be " \ "installed. To do this add the `rails-deprecated-sanitizer` to " \ "your Gemfile. Consult the Rails 4.2 upgrade guide for more information." end end end end end rails-rails-html-sanitizer-a8a0413/lib/rails/000077500000000000000000000000001514737066300211235ustar00rootroot00000000000000rails-rails-html-sanitizer-a8a0413/lib/rails/html/000077500000000000000000000000001514737066300220675ustar00rootroot00000000000000rails-rails-html-sanitizer-a8a0413/lib/rails/html/sanitizer.rb000066400000000000000000000351521514737066300244320ustar00rootroot00000000000000# frozen_string_literal: true module Rails module HTML class Sanitizer class << self def html5_support? return @html5_support if defined?(@html5_support) @html5_support = Loofah.respond_to?(:html5_support?) && Loofah.html5_support? end def best_supported_vendor html5_support? ? Rails::HTML5::Sanitizer : Rails::HTML4::Sanitizer end def allowed_uri?(uri_string) Loofah::HTML5::Scrub.allowed_uri?(uri_string) end end def sanitize(html, options = {}) raise NotImplementedError, "subclasses must implement sanitize method." end private def remove_xpaths(node, xpaths) node.xpath(*xpaths).remove node end def properly_encode(fragment, options) fragment.xml? ? fragment.to_xml(options) : fragment.to_html(options) end end module Concern module ComposedSanitize def sanitize(html, options = {}) return unless html return html if html.empty? serialize(scrub(parse_fragment(html), options)) end end module Parser module HTML4 def parse_fragment(html) Loofah.html4_fragment(html) end end module HTML5 def parse_fragment(html) Loofah.html5_fragment(html) end end if Rails::HTML::Sanitizer.html5_support? end module Scrubber module Full def scrub(fragment, options = {}) fragment.scrub!(TextOnlyScrubber.new) end end module Link def initialize super @link_scrubber = TargetScrubber.new @link_scrubber.tags = %w(a) @link_scrubber.attributes = %w(href) end def scrub(fragment, options = {}) fragment.scrub!(@link_scrubber) end end module SafeList # The default safe list for tags DEFAULT_ALLOWED_TAGS = Set.new([ "a", "abbr", "acronym", "address", "b", "big", "blockquote", "br", "cite", "code", "dd", "del", "dfn", "div", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "img", "ins", "kbd", "li", "mark", "ol", "p", "pre", "samp", "small", "span", "strong", "sub", "sup", "time", "tt", "ul", "var", ]).freeze # The default safe list for attributes DEFAULT_ALLOWED_ATTRIBUTES = Set.new([ "abbr", "alt", "cite", "class", "datetime", "height", "href", "lang", "name", "src", "title", "width", "xml:lang", ]).freeze def self.included(klass) class << klass attr_accessor :allowed_tags attr_accessor :allowed_attributes end klass.allowed_tags = DEFAULT_ALLOWED_TAGS.dup klass.allowed_attributes = DEFAULT_ALLOWED_ATTRIBUTES.dup end def initialize(prune: false) @permit_scrubber = PermitScrubber.new(prune: prune) end def scrub(fragment, options = {}) if scrubber = options[:scrubber] # No duck typing, Loofah ensures subclass of Loofah::Scrubber fragment.scrub!(scrubber) elsif allowed_tags(options) || allowed_attributes(options) @permit_scrubber.tags = allowed_tags(options) @permit_scrubber.attributes = allowed_attributes(options) fragment.scrub!(@permit_scrubber) else fragment.scrub!(:strip) end end def sanitize_css(style_string) Loofah::HTML5::Scrub.scrub_css(style_string) end private def allowed_tags(options) options[:tags] || self.class.allowed_tags end def allowed_attributes(options) options[:attributes] || self.class.allowed_attributes end end end module Serializer module UTF8Encode def serialize(fragment) properly_encode(fragment, encoding: "UTF-8") end end end end end module HTML4 module Sanitizer module VendorMethods def full_sanitizer Rails::HTML4::FullSanitizer end def link_sanitizer Rails::HTML4::LinkSanitizer end def safe_list_sanitizer Rails::HTML4::SafeListSanitizer end def white_list_sanitizer # :nodoc: safe_list_sanitizer end end extend VendorMethods end # == Rails::HTML4::FullSanitizer # # Removes all tags from HTML4 but strips out scripts, forms and comments. # # full_sanitizer = Rails::HTML4::FullSanitizer.new # full_sanitizer.sanitize("Bold no more! See more here...") # # => "Bold no more! See more here..." # class FullSanitizer < Rails::HTML::Sanitizer include HTML::Concern::ComposedSanitize include HTML::Concern::Parser::HTML4 include HTML::Concern::Scrubber::Full include HTML::Concern::Serializer::UTF8Encode end # == Rails::HTML4::LinkSanitizer # # Removes +a+ tags and +href+ attributes from HTML4 leaving only the link text. # # link_sanitizer = Rails::HTML4::LinkSanitizer.new # link_sanitizer.sanitize('Only the link text will be kept.') # # => "Only the link text will be kept." # class LinkSanitizer < Rails::HTML::Sanitizer include HTML::Concern::ComposedSanitize include HTML::Concern::Parser::HTML4 include HTML::Concern::Scrubber::Link include HTML::Concern::Serializer::UTF8Encode end # == Rails::HTML4::SafeListSanitizer # # Sanitizes HTML4 and CSS from an extensive safe list. # # === Whitespace # # We can't make any guarantees about whitespace being kept or stripped. Loofah uses Nokogiri, # which wraps either a C or Java parser for the respective Ruby implementation. Those two # parsers determine how whitespace is ultimately handled. # # When the stripped markup will be rendered the users browser won't take whitespace into account # anyway. It might be better to suggest your users wrap their whitespace sensitive content in # pre tags or that you do so automatically. # # === Options # # Sanitizes both html and css via the safe lists found in # Rails::HTML::Concern::Scrubber::SafeList # # SafeListSanitizer also accepts options to configure the safe list used when sanitizing html. # There's a class level option: # # Rails::HTML4::SafeListSanitizer.allowed_tags = %w(table tr td) # Rails::HTML4::SafeListSanitizer.allowed_attributes = %w(id class style) # # Tags and attributes can also be passed to +sanitize+. Passed options take precedence over the # class level options. # # === Examples # # safe_list_sanitizer = Rails::HTML4::SafeListSanitizer.new # # # default: sanitize via a extensive safe list of allowed elements # safe_list_sanitizer.sanitize(@article.body) # # # sanitize via the supplied tags and attributes # safe_list_sanitizer.sanitize( # @article.body, # tags: %w(table tr td), # attributes: %w(id class style), # ) # # # sanitize via a custom Loofah scrubber # safe_list_sanitizer.sanitize(@article.body, scrubber: ArticleScrubber.new) # # # prune nodes from the tree instead of stripping tags and leaving inner content # safe_list_sanitizer = Rails::HTML4::SafeListSanitizer.new(prune: true) # # # the sanitizer can also sanitize CSS # safe_list_sanitizer.sanitize_css('background-color: #000;') # class SafeListSanitizer < Rails::HTML::Sanitizer include HTML::Concern::ComposedSanitize include HTML::Concern::Parser::HTML4 include HTML::Concern::Scrubber::SafeList include HTML::Concern::Serializer::UTF8Encode end end module HTML5 class Sanitizer class << self def full_sanitizer Rails::HTML5::FullSanitizer end def link_sanitizer Rails::HTML5::LinkSanitizer end def safe_list_sanitizer Rails::HTML5::SafeListSanitizer end def white_list_sanitizer # :nodoc: safe_list_sanitizer end end end # == Rails::HTML5::FullSanitizer # # Removes all tags from HTML5 but strips out scripts, forms and comments. # # full_sanitizer = Rails::HTML5::FullSanitizer.new # full_sanitizer.sanitize("Bold no more! See more here...") # # => "Bold no more! See more here..." # class FullSanitizer < Rails::HTML::Sanitizer include HTML::Concern::ComposedSanitize include HTML::Concern::Parser::HTML5 include HTML::Concern::Scrubber::Full include HTML::Concern::Serializer::UTF8Encode end # == Rails::HTML5::LinkSanitizer # # Removes +a+ tags and +href+ attributes from HTML5 leaving only the link text. # # link_sanitizer = Rails::HTML5::LinkSanitizer.new # link_sanitizer.sanitize('Only the link text will be kept.') # # => "Only the link text will be kept." # class LinkSanitizer < Rails::HTML::Sanitizer include HTML::Concern::ComposedSanitize include HTML::Concern::Parser::HTML5 include HTML::Concern::Scrubber::Link include HTML::Concern::Serializer::UTF8Encode end # == Rails::HTML5::SafeListSanitizer # # Sanitizes HTML5 and CSS from an extensive safe list. # # === Whitespace # # We can't make any guarantees about whitespace being kept or stripped. Loofah uses Nokogiri, # which wraps either a C or Java parser for the respective Ruby implementation. Those two # parsers determine how whitespace is ultimately handled. # # When the stripped markup will be rendered the users browser won't take whitespace into account # anyway. It might be better to suggest your users wrap their whitespace sensitive content in # pre tags or that you do so automatically. # # === Options # # Sanitizes both html and css via the safe lists found in # Rails::HTML::Concern::Scrubber::SafeList # # SafeListSanitizer also accepts options to configure the safe list used when sanitizing html. # There's a class level option: # # Rails::HTML5::SafeListSanitizer.allowed_tags = %w(table tr td) # Rails::HTML5::SafeListSanitizer.allowed_attributes = %w(id class style) # # Tags and attributes can also be passed to +sanitize+. Passed options take precedence over the # class level options. # # === Examples # # safe_list_sanitizer = Rails::HTML5::SafeListSanitizer.new # # # default: sanitize via a extensive safe list of allowed elements # safe_list_sanitizer.sanitize(@article.body) # # # sanitize via the supplied tags and attributes # safe_list_sanitizer.sanitize( # @article.body, # tags: %w(table tr td), # attributes: %w(id class style), # ) # # # sanitize via a custom Loofah scrubber # safe_list_sanitizer.sanitize(@article.body, scrubber: ArticleScrubber.new) # # # prune nodes from the tree instead of stripping tags and leaving inner content # safe_list_sanitizer = Rails::HTML5::SafeListSanitizer.new(prune: true) # # # the sanitizer can also sanitize CSS # safe_list_sanitizer.sanitize_css('background-color: #000;') # class SafeListSanitizer < Rails::HTML::Sanitizer include HTML::Concern::ComposedSanitize include HTML::Concern::Parser::HTML5 include HTML::Concern::Scrubber::SafeList include HTML::Concern::Serializer::UTF8Encode end end if Rails::HTML::Sanitizer.html5_support? module HTML Sanitizer.extend(HTML4::Sanitizer::VendorMethods) # :nodoc: FullSanitizer = HTML4::FullSanitizer # :nodoc: LinkSanitizer = HTML4::LinkSanitizer # :nodoc: SafeListSanitizer = HTML4::SafeListSanitizer # :nodoc: WhiteListSanitizer = SafeListSanitizer # :nodoc: end end rails-rails-html-sanitizer-a8a0413/lib/rails/html/sanitizer/000077500000000000000000000000001514737066300240775ustar00rootroot00000000000000rails-rails-html-sanitizer-a8a0413/lib/rails/html/sanitizer/version.rb000066400000000000000000000001701514737066300261070ustar00rootroot00000000000000# frozen_string_literal: true module Rails module HTML class Sanitizer VERSION = "1.7.0" end end end rails-rails-html-sanitizer-a8a0413/lib/rails/html/scrubbers.rb000066400000000000000000000155651514737066300244220ustar00rootroot00000000000000# frozen_string_literal: true module Rails module HTML # === Rails::HTML::PermitScrubber # # +Rails::HTML::PermitScrubber+ allows you to permit only your own tags and/or attributes. # # +Rails::HTML::PermitScrubber+ can be subclassed to determine: # - When a node should be skipped via +skip_node?+. # - When a node is allowed via +allowed_node?+. # - When an attribute should be scrubbed via +scrub_attribute?+. # # Subclasses don't need to worry if tags or attributes are set or not. # If tags or attributes are not set, Loofah's behavior will be used. # If you override +allowed_node?+ and no tags are set, it will not be called. # Instead Loofahs behavior will be used. # Likewise for +scrub_attribute?+ and attributes respectively. # # Text and CDATA nodes are skipped by default. # Unallowed elements will be stripped, i.e. element is removed but its subtree kept. # Supplied tags and attributes should be Enumerables. # # +tags=+ # If set, elements excluded will be stripped. # If not, elements are stripped based on Loofahs +HTML5::Scrub.allowed_element?+. # # +attributes=+ # If set, attributes excluded will be removed. # If not, attributes are removed based on Loofahs +HTML5::Scrub.scrub_attributes+. # # class CommentScrubber < Rails::HTML::PermitScrubber # def initialize # super # self.tags = %w(form script comment blockquote) # end # # def skip_node?(node) # node.text? # end # # def scrub_attribute?(name) # name == "style" # end # end # # See the documentation for +Nokogiri::XML::Node+ to understand what's possible # with nodes: https://nokogiri.org/rdoc/Nokogiri/XML/Node.html class PermitScrubber < Loofah::Scrubber attr_reader :tags, :attributes, :prune def initialize(prune: false) @prune = prune @direction = @prune ? :top_down : :bottom_up @tags, @attributes = nil, nil end def tags=(tags) @tags = validate!(tags.dup, :tags) end def attributes=(attributes) @attributes = validate!(attributes.dup, :attributes) end def scrub(node) if Loofah::HTML5::Scrub.cdata_needs_escaping?(node) replacement = Loofah::HTML5::Scrub.cdata_escape(node) node.replace(replacement) return CONTINUE end return CONTINUE if skip_node?(node) unless (node.element? || node.comment?) && keep_node?(node) return STOP unless scrub_node(node) == CONTINUE end scrub_attributes(node) CONTINUE end protected def allowed_node?(node) @tags.include?(node.name) end def skip_node?(node) node.text? end def scrub_attribute?(name) !@attributes.include?(name) end def keep_node?(node) if @tags allowed_node?(node) else Loofah::HTML5::Scrub.allowed_element?(node.name) end end def scrub_node(node) # If a node has a namespace, then it's a tag in either a `math` or `svg` foreign context, # and we should always prune it to avoid namespace confusion and mutation XSS vectors. unless prune || node.namespace node.before(node.children) end node.remove end def scrub_attributes(node) if @attributes node.attribute_nodes.each do |attr| if scrub_attribute?(attr.name) attr.remove else scrub_attribute(node, attr) end end scrub_css_attribute(node) else Loofah::HTML5::Scrub.scrub_attributes(node) end end def scrub_css_attribute(node) if Loofah::HTML5::Scrub.respond_to?(:scrub_css_attribute) Loofah::HTML5::Scrub.scrub_css_attribute(node) else style = node.attributes["style"] style.value = Loofah::HTML5::Scrub.scrub_css(style.value) if style end end def validate!(var, name) if var && !var.is_a?(Enumerable) raise ArgumentError, "You should pass :#{name} as an Enumerable" end if var && name == :tags if var.include?("mglyph") warn("WARNING: 'mglyph' tags cannot be allowed by the PermitScrubber and will be scrubbed") var.delete("mglyph") end if var.include?("malignmark") warn("WARNING: 'malignmark' tags cannot be allowed by the PermitScrubber and will be scrubbed") var.delete("malignmark") end if var.include?("noscript") warn("WARNING: 'noscript' tags cannot be allowed by the PermitScrubber and will be scrubbed") var.delete("noscript") end end var end def scrub_attribute(node, attr_node) attr_name = if attr_node.namespace "#{attr_node.namespace.prefix}:#{attr_node.node_name}" else attr_node.node_name end return if Loofah::HTML5::SafeList::ATTR_VAL_IS_URI.include?(attr_name) && Loofah::HTML5::Scrub.scrub_uri_attribute(attr_node) if Loofah::HTML5::SafeList::SVG_ATTR_VAL_ALLOWS_REF.include?(attr_name) Loofah::HTML5::Scrub.scrub_attribute_that_allows_local_ref(attr_node) end if Loofah::HTML5::SafeList::SVG_ALLOW_LOCAL_HREF.include?(node.name) && attr_name == "xlink:href" && attr_node.value =~ /^\s*[^#\s].*/m attr_node.remove end node.remove_attribute(attr_node.name) if attr_name == "src" && attr_node.value !~ /[^[:space:]]/ Loofah::HTML5::Scrub.force_correct_attribute_escaping! node end end # === Rails::HTML::TargetScrubber # # Where +Rails::HTML::PermitScrubber+ picks out tags and attributes to permit in # sanitization, +Rails::HTML::TargetScrubber+ targets them for removal. # # +tags=+ # If set, elements included will be stripped. # # +attributes=+ # If set, attributes included will be removed. class TargetScrubber < PermitScrubber def allowed_node?(node) !super end def scrub_attribute?(name) !super end end # === Rails::HTML::TextOnlyScrubber # # +Rails::HTML::TextOnlyScrubber+ allows you to permit text nodes. # # Unallowed elements will be stripped, i.e. element is removed but its subtree kept. class TextOnlyScrubber < Loofah::Scrubber def initialize @direction = :bottom_up end def scrub(node) if node.text? CONTINUE else node.before node.children node.remove end end end end end rails-rails-html-sanitizer-a8a0413/rails-html-sanitizer.gemspec000066400000000000000000000032231514737066300246720ustar00rootroot00000000000000# coding: utf-8 # frozen_string_literal: true require_relative "lib/rails/html/sanitizer/version" Gem::Specification.new do |spec| spec.name = "rails-html-sanitizer" spec.version = Rails::HTML::Sanitizer::VERSION spec.authors = ["Rafael Mendonça França", "Kasper Timm Hansen", "Mike Dalessio"] spec.email = ["rafaelmfranca@gmail.com", "kaspth@gmail.com", "mike.dalessio@gmail.com"] spec.description = "HTML sanitization for Rails applications" spec.summary = "This gem is responsible to sanitize HTML fragments in Rails applications." spec.homepage = "https://github.com/rails/rails-html-sanitizer" spec.license = "MIT" spec.required_ruby_version = ">= 2.7.0" spec.metadata = { "bug_tracker_uri" => "https://github.com/rails/rails-html-sanitizer/issues", "changelog_uri" => "https://github.com/rails/rails-html-sanitizer/blob/v#{spec.version}/CHANGELOG.md", "documentation_uri" => "https://www.rubydoc.info/gems/rails-html-sanitizer/#{spec.version}", "source_code_uri" => "https://github.com/rails/rails-html-sanitizer/tree/v#{spec.version}", } spec.files = Dir["lib/**/*", "README.md", "MIT-LICENSE", "CHANGELOG.md"] spec.test_files = Dir["test/**/*"] spec.require_paths = ["lib"] spec.add_dependency "loofah", "~> 2.25" # A fix was shipped in nokogiri v1.15.7 and v1.16.8 without which there is a vulnerability in this gem. spec.add_dependency "nokogiri", [">=1.15.7", "!=1.16.0", "!=1.16.0.rc1", "!=1.16.1", "!=1.16.2", "!=1.16.3", "!=1.16.4", "!=1.16.5", "!=1.16.6", "!=1.16.7"] end rails-rails-html-sanitizer-a8a0413/test/000077500000000000000000000000001514737066300202225ustar00rootroot00000000000000rails-rails-html-sanitizer-a8a0413/test/rails_api_test.rb000066400000000000000000000076441514737066300235640ustar00rootroot00000000000000# frozen_string_literal: true require_relative "test_helper" class RailsApiTest < Minitest::Test def test_html_module_name_alias assert_equal(Rails::Html, Rails::HTML) assert_equal("Rails::HTML", Rails::Html.name) assert_equal("Rails::HTML", Rails::HTML.name) end def test_html_scrubber_class_names assert(Rails::Html::PermitScrubber) assert(Rails::Html::TargetScrubber) assert(Rails::Html::TextOnlyScrubber) assert(Rails::Html::Sanitizer) end def test_best_supported_vendor_when_html5_is_not_supported_returns_html4 Rails::HTML::Sanitizer.stub(:html5_support?, false) do assert_equal(Rails::HTML4::Sanitizer, Rails::HTML::Sanitizer.best_supported_vendor) end end def test_best_supported_vendor_when_html5_is_supported_returns_html5 skip("no HTML5 support on this platform") unless Rails::HTML::Sanitizer.html5_support? Rails::HTML::Sanitizer.stub(:html5_support?, true) do assert_equal(Rails::HTML5::Sanitizer, Rails::HTML::Sanitizer.best_supported_vendor) end end def test_html4_sanitizer_alias_full assert_equal(Rails::HTML4::FullSanitizer, Rails::HTML::FullSanitizer) assert_equal("Rails::HTML4::FullSanitizer", Rails::HTML::FullSanitizer.name) end def test_html4_sanitizer_alias_link assert_equal(Rails::HTML4::LinkSanitizer, Rails::HTML::LinkSanitizer) assert_equal("Rails::HTML4::LinkSanitizer", Rails::HTML::LinkSanitizer.name) end def test_html4_sanitizer_alias_safe_list assert_equal(Rails::HTML4::SafeListSanitizer, Rails::HTML::SafeListSanitizer) assert_equal("Rails::HTML4::SafeListSanitizer", Rails::HTML::SafeListSanitizer.name) end def test_html4_full_sanitizer assert_equal(Rails::HTML4::FullSanitizer, Rails::HTML::Sanitizer.full_sanitizer) assert_equal(Rails::HTML4::FullSanitizer, Rails::HTML4::Sanitizer.full_sanitizer) end def test_html4_link_sanitizer assert_equal(Rails::HTML4::LinkSanitizer, Rails::HTML::Sanitizer.link_sanitizer) assert_equal(Rails::HTML4::LinkSanitizer, Rails::HTML4::Sanitizer.link_sanitizer) end def test_html4_safe_list_sanitizer assert_equal(Rails::HTML4::SafeListSanitizer, Rails::HTML::Sanitizer.safe_list_sanitizer) assert_equal(Rails::HTML4::SafeListSanitizer, Rails::HTML4::Sanitizer.safe_list_sanitizer) end def test_html4_white_list_sanitizer assert_equal(Rails::HTML4::SafeListSanitizer, Rails::HTML::Sanitizer.white_list_sanitizer) assert_equal(Rails::HTML4::SafeListSanitizer, Rails::HTML4::Sanitizer.white_list_sanitizer) end def test_html5_full_sanitizer skip("no HTML5 support on this platform") unless Rails::HTML::Sanitizer.html5_support? assert_equal(Rails::HTML5::FullSanitizer, Rails::HTML5::Sanitizer.full_sanitizer) end def test_html5_link_sanitizer skip("no HTML5 support on this platform") unless Rails::HTML::Sanitizer.html5_support? assert_equal(Rails::HTML5::LinkSanitizer, Rails::HTML5::Sanitizer.link_sanitizer) end def test_html5_safe_list_sanitizer skip("no HTML5 support on this platform") unless Rails::HTML::Sanitizer.html5_support? assert_equal(Rails::HTML5::SafeListSanitizer, Rails::HTML5::Sanitizer.safe_list_sanitizer) end def test_html5_white_list_sanitizer skip("no HTML5 support on this platform") unless Rails::HTML::Sanitizer.html5_support? assert_equal(Rails::HTML5::SafeListSanitizer, Rails::HTML5::Sanitizer.white_list_sanitizer) end def test_allowed_uri_returns_true_for_allowed_protocols assert(Rails::HTML::Sanitizer.allowed_uri?("https://example.com")) assert(Rails::HTML::Sanitizer.allowed_uri?("http://example.com")) assert(Rails::HTML::Sanitizer.allowed_uri?("mailto:user@example.com")) end def test_allowed_uri_returns_false_for_disallowed_protocols refute(Rails::HTML::Sanitizer.allowed_uri?("javascript:alert(1)")) end def test_allowed_uri_returns_true_for_relative_uris assert(Rails::HTML::Sanitizer.allowed_uri?("/relative/path")) end end rails-rails-html-sanitizer-a8a0413/test/sanitizer_test.rb000066400000000000000000001325721514737066300236300ustar00rootroot00000000000000# frozen_string_literal: true require_relative "test_helper" # # NOTE that many of these tests contain multiple acceptable results. # # In some cases, this is because of how the HTML4 parser's recovery behavior changed in libxml2 # 2.9.14 and 2.10.0. For more details, see: # # - https://github.com/sparklemotion/nokogiri/releases/tag/v1.13.5 # - https://gitlab.gnome.org/GNOME/libxml2/-/issues/380 # # In other cases, multiple acceptable results are provided because Nokogiri's vendored libxml2 is # patched to entity-escape server-side includes (aks "SSI", aka ``). # # In many other cases, it's because the parser used by Nokogiri on JRuby (xerces+nekohtml) parses # slightly differently than libxml2 in edge cases. # module SanitizerTests def self.loofah_html5_support? Loofah.respond_to?(:html5_support?) && Loofah.html5_support? end class BaseSanitizerTest < Minitest::Test class XpathRemovalTestSanitizer < Rails::HTML::Sanitizer def sanitize(html, options = {}) fragment = Loofah.fragment(html) remove_xpaths(fragment, options[:xpaths]).to_s end end def test_sanitizer_sanitize_raises_not_implemented_error assert_raises NotImplementedError do Rails::HTML::Sanitizer.new.sanitize("asdf") end end def test_remove_xpaths_removes_an_xpath html = %(

hello

) assert_equal %(

hello

), xpath_sanitize(html, xpaths: %w(.//script)) end def test_remove_xpaths_removes_all_occurrences_of_xpath html = %(

hello

) assert_equal %(

hello

), xpath_sanitize(html, xpaths: %w(.//script)) end def test_remove_xpaths_called_with_faulty_xpath assert_raises Nokogiri::XML::XPath::SyntaxError do xpath_sanitize("

hello

", xpaths: %w(..faulty_xpath)) end end def test_remove_xpaths_called_with_xpath_string assert_equal "", xpath_sanitize("", xpaths: ".//a") end def test_remove_xpaths_called_with_enumerable_xpaths assert_equal "", xpath_sanitize("", xpaths: %w(.//a .//span)) end protected def xpath_sanitize(input, options = {}) XpathRemovalTestSanitizer.new.sanitize(input, options) end end module ModuleUnderTest def module_under_test self.class.instance_variable_get(:@module_under_test) end end module FullSanitizerTest include ModuleUnderTest def test_strip_tags_with_quote input = '<" hi' result = full_sanitize(input) acceptable_results = [ # libxml2 >= 2.9.14 and xerces+neko %{<" hi}, # other libxml2 %{ hi}, ] assert_includes(acceptable_results, result) end def test_strip_invalid_html assert_equal "<<", full_sanitize("<<This is a test.

\n\n\n\n

It no longer contains any HTML.

\n} assert_equal expected, full_sanitize(input) end def test_remove_unclosed_tags input = "This is <-- not\n a comment here." result = full_sanitize(input) acceptable_results = [ # libxml2 >= 2.9.14 and xerces+neko %{This is <-- not\n a comment here.}, # other libxml2 %{This is }, ] assert_includes(acceptable_results, result) end def test_strip_cdata input = "This has a ]]> here." result = full_sanitize(input) acceptable_results = [ # libxml2 = 2.9.14 %{This has a <![CDATA[]]> here.}, # other libxml2 %{This has a ]]> here.}, # xerces+neko %{This has a here.}, ] assert_includes(acceptable_results, result) end def test_strip_blank_string assert_nil full_sanitize(nil) assert_equal "", full_sanitize("") assert_equal " ", full_sanitize(" ") end def test_strip_tags_with_plaintext assert_equal "Don't touch me", full_sanitize("Don't touch me") end def test_strip_tags_with_tags assert_equal "This is a test.", full_sanitize("

This is a test.

") end def test_escape_tags_with_many_open_quotes assert_equal "<<", full_sanitize("<<") end def test_strip_tags_with_sentence assert_equal "This is a test.", full_sanitize("This is a test.") end def test_strip_tags_with_comment assert_equal "This has a here.", full_sanitize("This has a here.") end def test_strip_tags_with_frozen_string assert_equal "Frozen string with no tags", full_sanitize("Frozen string with no tags") end def test_full_sanitize_respect_html_escaping_of_the_given_string assert_equal 'test\r\nstring', full_sanitize('test\r\nstring') assert_equal "&", full_sanitize("&") assert_equal "&", full_sanitize("&") assert_equal "&amp;", full_sanitize("&amp;") assert_equal "omg <script>BOM</script>", full_sanitize("omg <script>BOM</script>") end def test_sanitize_ascii_8bit_string full_sanitize("
hello
".encode("ASCII-8BIT")).tap do |sanitized| assert_equal "hello", sanitized assert_equal Encoding::UTF_8, sanitized.encoding end end protected def full_sanitize(input, options = {}) module_under_test::FullSanitizer.new.sanitize(input, options) end end class HTML4FullSanitizerTest < Minitest::Test @module_under_test = Rails::HTML4 include FullSanitizerTest end class HTML5FullSanitizerTest < Minitest::Test @module_under_test = Rails::HTML5 include FullSanitizerTest end if loofah_html5_support? module LinkSanitizerTest include ModuleUnderTest def test_strip_links_with_tags_in_tags expected = "<a href='hello'>all day long</a>" input = "<a href='hello'>all day long</a>" assert_equal expected, link_sanitize(input) end def test_strip_links_with_unclosed_tags assert_equal "", link_sanitize("on my mind\nall day long") end def test_strip_links_leaves_nonlink_tags assert_equal "My mind\nall day long", link_sanitize("My mind\nall day long") end def test_strip_links_with_links assert_equal "0wn3d", link_sanitize("0wn3d") end def test_strip_links_with_linkception assert_equal "Magic", link_sanitize("Magic") end def test_sanitize_ascii_8bit_string link_sanitize("
hello
".encode("ASCII-8BIT")).tap do |sanitized| assert_equal "
hello
", sanitized assert_equal Encoding::UTF_8, sanitized.encoding end end protected def link_sanitize(input, options = {}) module_under_test::LinkSanitizer.new.sanitize(input, options) end end class HTML4LinkSanitizerTest < Minitest::Test @module_under_test = Rails::HTML4 include LinkSanitizerTest end class HTML5LinkSanitizerTest < Minitest::Test @module_under_test = Rails::HTML5 include LinkSanitizerTest end if loofah_html5_support? module SafeListSanitizerTest include ModuleUnderTest def test_sanitize_nested_script assert_equal '<script>alert("XSS");</script>', safe_list_sanitize('alert("XSS");/', tags: %w(em)) end def test_sanitize_nested_script_in_style input = 'alert("XSS");/' result = safe_list_sanitize(input, tags: %w(em)) acceptable_results = [ # libxml2 %{<script>alert("XSS");</script>}, # xerces+neko. unavoidable double-escaping, see loofah/docs/2022-10-decision-on-cdata-nodes.md %{&lt;script&gt;alert(\"XSS\");&lt;&lt;/style&gt;/script&gt;}, ] assert_includes(acceptable_results, result) end def test_strip_unclosed_cdata input = "This has an unclosed ]] here..." result = safe_list_sanitize(input) acceptable_results = [ # libxml2 = 2.9.14 %{This has an unclosed <![CDATA[]] here...}, # other libxml2 %{This has an unclosed ]] here...}, # xerces+neko %{This has an unclosed } ] assert_includes(acceptable_results, result) end def test_sanitize_form assert_sanitized "
", "" end def test_sanitize_plaintext # note that the `plaintext` tag has been deprecated since HTML 2 # https://developer.mozilla.org/en-US/docs/Web/HTML/Element/plaintext input = "<span>foo</span></plaintext>" result = safe_list_sanitize(input) acceptable_results = [ # libxml2 "<span>foo</span>", # xerces+nekohtml-unit "&lt;span&gt;foo&lt;/span&gt;&lt;/plaintext&gt;", # xerces+cyberneko "&lt;span&gt;foo&lt;/span&gt;" ] assert_includes(acceptable_results, result) end def test_sanitize_script assert_sanitized "a b c<script language=\"Javascript\">blah blah blah</script>d e f", "a b cblah blah blahd e f" end def test_sanitize_js_handlers raw = %{onthis="do that" <a href="#" onclick="hello" name="foo" onbogus="remove me">hello</a>} assert_sanitized raw, %{onthis="do that" <a href="#" name="foo">hello</a>} end def test_sanitize_javascript_href raw = %{href="javascript:bang" <a href="javascript:bang" name="hello">foo</a>, <span href="javascript:bang">bar</span>} assert_sanitized raw, %{href="javascript:bang" <a name="hello">foo</a>, <span>bar</span>} end def test_sanitize_image_src raw = %{src="javascript:bang" <img src="javascript:bang" width="5">foo</img>, <span src="javascript:bang">bar</span>} assert_sanitized raw, %{src="javascript:bang" <img width="5">foo, <span>bar</span>} end def test_should_allow_anchors assert_sanitized %(<a href="foo" onclick="bar"><script>baz</script></a>), %(<a href=\"foo\">baz</a>) end def test_video_poster_sanitization scope_allowed_tags(%w(video)) do scope_allowed_attributes %w(src poster) do expected = if RUBY_PLATFORM == "java" # xerces+nekohtml alphabetizes the attributes! FML. %(<video poster="posterimage.jpg" src="videofile.ogg"></video>) else %(<video src="videofile.ogg" poster="posterimage.jpg"></video>) end assert_sanitized( %(<video src="videofile.ogg" autoplay poster="posterimage.jpg"></video>), expected, ) assert_sanitized( %(<video src="videofile.ogg" poster=javascript:alert(1)></video>), %(<video src="videofile.ogg"></video>), ) end end end # RFC 3986, sec 4.2 def test_allow_colons_in_path_component assert_sanitized "<a href=\"./this:that\">foo</a>" end %w(src width height alt).each do |img_attr| define_method "test_should_allow_image_#{img_attr}_attribute" do assert_sanitized %(<img #{img_attr}="foo" onclick="bar" />), %(<img #{img_attr}="foo">) end end def test_lang_and_xml_lang # https://html.spec.whatwg.org/multipage/dom.html#the-lang-and-xml:lang-attributes # # 3.2.6.2 The lang and xml:lang attributes # # ... Authors must not use the lang attribute in the XML namespace on HTML elements in HTML # documents. To ease migration to and from XML, authors may specify an attribute in no namespace # with no prefix and with the literal localname "xml:lang" on HTML elements in HTML documents, # but such attributes must only be specified if a lang attribute in no namespace is also # specified, and both attributes must have the same value when compared in an ASCII # case-insensitive manner. input = expected = "<div lang=\"en\" xml:lang=\"en\">foo</div>" assert_sanitized(input, expected) end def test_should_handle_non_html assert_sanitized "abc" end def test_should_handle_blank_text assert_nil(safe_list_sanitize(nil)) assert_equal("", safe_list_sanitize("")) assert_equal(" ", safe_list_sanitize(" ")) end def test_setting_allowed_tags_affects_sanitization scope_allowed_tags %w(u) do |sanitizer| assert_equal "<u></u>", sanitizer.sanitize("<a><u></u></a>") end end def test_setting_allowed_attributes_affects_sanitization scope_allowed_attributes %w(foo) do |sanitizer| input = '<a foo="hello" bar="world"></a>' assert_equal '<a foo="hello"></a>', sanitizer.sanitize(input) end end def test_custom_tags_overrides_allowed_tags scope_allowed_tags %(u) do |sanitizer| input = "<a><u></u></a>" assert_equal "<a></a>", sanitizer.sanitize(input, tags: %w(a)) end end def test_custom_attributes_overrides_allowed_attributes scope_allowed_attributes %(foo) do |sanitizer| input = '<a foo="hello" bar="world"></a>' assert_equal '<a bar="world"></a>', sanitizer.sanitize(input, attributes: %w(bar)) end end def test_should_allow_prune sanitizer = module_under_test::SafeListSanitizer.new(prune: true) text = "<u>leave me <b>now</b></u>" assert_equal "<u>leave me </u>", sanitizer.sanitize(text, tags: %w(u)) end def test_should_allow_custom_tags text = "<u>foo</u>" assert_equal text, safe_list_sanitize(text, tags: %w(u)) end def test_should_allow_only_custom_tags text = "<u>foo</u> with <i>bar</i>" assert_equal "<u>foo</u> with bar", safe_list_sanitize(text, tags: %w(u)) end def test_should_allow_custom_tags_with_attributes text = %(<blockquote cite="http://example.com/">foo</blockquote>) assert_equal text, safe_list_sanitize(text) end def test_should_allow_custom_tags_with_custom_attributes text = %(<blockquote foo="bar">Lorem ipsum</blockquote>) assert_equal text, safe_list_sanitize(text, attributes: ["foo"]) end def test_scrub_style_if_style_attribute_option_is_passed input = '<p style="color: #000; background-image: url(http://www.ragingplatypus.com/i/cam-full.jpg);"></p>' actual = safe_list_sanitize(input, attributes: %w(style)) assert_includes(['<p style="color: #000;"></p>', '<p style="color:#000;"></p>'], actual) end def test_should_raise_argument_error_if_tags_is_not_enumerable assert_raises ArgumentError do safe_list_sanitize("<a>some html</a>", tags: "foo") end end def test_should_raise_argument_error_if_attributes_is_not_enumerable assert_raises ArgumentError do safe_list_sanitize("<a>some html</a>", attributes: "foo") end end def test_should_not_accept_non_loofah_inheriting_scrubber scrubber = Object.new def scrubber.scrub(node); node.name = "h1"; end assert_raises Loofah::ScrubberNotFound do safe_list_sanitize("<a>some html</a>", scrubber: scrubber) end end def test_should_accept_loofah_inheriting_scrubber scrubber = Loofah::Scrubber.new def scrubber.scrub(node); node.replace("<h1>#{node.inner_html}</h1>"); end html = "<script>hello!</script>" assert_equal "<h1>hello!</h1>", safe_list_sanitize(html, scrubber: scrubber) end def test_should_accept_loofah_scrubber_that_wraps_a_block scrubber = Loofah::Scrubber.new { |node| node.replace("<h1>#{node.inner_html}</h1>") } html = "<script>hello!</script>" assert_equal "<h1>hello!</h1>", safe_list_sanitize(html, scrubber: scrubber) end def test_custom_scrubber_takes_precedence_over_other_options scrubber = Loofah::Scrubber.new { |node| node.replace("<h1>#{node.inner_html}</h1>") } html = "<script>hello!</script>" assert_equal "<h1>hello!</h1>", safe_list_sanitize(html, scrubber: scrubber, tags: ["foo"]) end def test_should_strip_src_attribute_in_img_with_bad_protocols assert_sanitized %(<img src="javascript:bang" title="1">), %(<img title="1">) end def test_should_strip_href_attribute_in_a_with_bad_protocols assert_sanitized %(<a href="javascript:bang" title="1">boo</a>), %(<a title="1">boo</a>) end def test_should_block_script_tag assert_sanitized %(<SCRIPT\nSRC=http://ha.ckers.org/xss.js></SCRIPT>), "" end def test_should_not_fall_for_xss_image_hack_with_uppercase_tags assert_sanitized %(<IMG """><SCRIPT>alert("XSS")</SCRIPT>">), %(<img>alert("XSS")"&gt;) end [%(<IMG SRC="javascript:alert('XSS');">), %(<IMG SRC=javascript:alert('XSS')>), %(<IMG SRC=JaVaScRiPt:alert('XSS')>), %(<IMG SRC=javascript:alert(&quot;XSS&quot;)>), %(<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>), %(<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>), %(<IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>), %(<IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>), %(<IMG SRC="jav\tascript:alert('XSS');">), %(<IMG SRC="jav&#x09;ascript:alert('XSS');">), %(<IMG SRC="jav&#x0A;ascript:alert('XSS');">), %(<IMG SRC="jav&#x0D;ascript:alert('XSS');">), %(<IMG SRC=" &#14; javascript:alert('XSS');">), %(<IMG SRC="javascript&#x3a;alert('XSS');">), %(<IMG SRC=`javascript:alert("RSnake says, 'XSS'")`>)].each do |img_hack| define_method "test_should_not_fall_for_xss_image_hack_#{img_hack}" do assert_sanitized img_hack, "<img>" end end def test_should_sanitize_tag_broken_up_by_null input = %(<SCR\0IPT>alert(\"XSS\")</SCR\0IPT>) result = safe_list_sanitize(input) acceptable_results = [ # libxml2 "", # xerces+neko 'alert("XSS")', ] assert_includes(acceptable_results, result) end def test_should_sanitize_invalid_script_tag assert_sanitized %(<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT>), "" end def test_should_sanitize_script_tag_with_multiple_open_brackets assert_sanitized %(<<SCRIPT>alert("XSS");//<</SCRIPT>), "&lt;alert(\"XSS\");//&lt;" end def test_should_sanitize_script_tag_with_multiple_open_brackets_2 input = %(<iframe src=http://ha.ckers.org/scriptlet.html\n<a) result = safe_list_sanitize(input) acceptable_results = [ # libxml2 "", # xerces+neko "&lt;a", ] assert_includes(acceptable_results, result) end def test_should_sanitize_unclosed_script assert_sanitized %(<SCRIPT SRC=http://ha.ckers.org/xss.js?<B>), "" end def test_should_sanitize_half_open_scripts input = %(<IMG SRC="javascript:alert('XSS')") result = safe_list_sanitize(input) acceptable_results = [ # libxml2 "<img>", # libgumbo "", ] assert_includes(acceptable_results, result) end def test_should_not_fall_for_ridiculous_hack img_hack = %(<IMG\nSRC\n=\n"\nj\na\nv\na\ns\nc\nr\ni\np\nt\n:\na\nl\ne\nr\nt\n(\n'\nX\nS\nS\n'\n)\n"\n>) assert_sanitized img_hack, "<img>" end def test_should_sanitize_attributes input = %(<SPAN title="'><script>alert()</script>">blah</SPAN>) result = safe_list_sanitize(input) acceptable_results = [ # libxml2 %(<span title="'&gt;&lt;script&gt;alert()&lt;/script&gt;">blah</span>), # libgumbo # this looks scary, but it's fine. for a more detailed analysis check out: # https://github.com/discourse/discourse/pull/21522#issuecomment-1545697968 %(<span title="'><script>alert()</script>">blah</span>) ] assert_includes(acceptable_results, result) end def test_should_sanitize_invalid_tag_names assert_sanitized(%(a b c<script/XSS src="http://ha.ckers.org/xss.js"></script>d e f), "a b cd e f") end def test_should_sanitize_non_alpha_and_non_digit_characters_in_tags assert_sanitized('<a onclick!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>foo</a>', "<a>foo</a>") end def test_should_sanitize_invalid_tag_names_in_single_tags input = %(<img/src="http://ha.ckers.org/xss.js"/>) result = safe_list_sanitize(input) acceptable_results = [ # libxml2 "<img>", # libgumbo %(<img src="http://ha.ckers.org/xss.js">), ] assert_includes(acceptable_results, result) end def test_should_sanitize_img_dynsrc_lowsrc assert_sanitized(%(<img lowsrc="javascript:alert('XSS')" />), "<img>") end def test_should_sanitize_img_vbscript assert_sanitized %(<img src='vbscript:msgbox("XSS")' />), "<img>" end def test_should_sanitize_cdata_section input = "<![CDATA[<span>section</span>]]>" result = safe_list_sanitize(input) acceptable_results = [ # libxml2 = 2.9.14 %{&lt;![CDATA[<span>section</span>]]&gt;}, # other libxml2 %{section]]&gt;}, # xerces+neko "", ] assert_includes(acceptable_results, result) end def test_should_sanitize_unterminated_cdata_section input = "<![CDATA[<span>neverending..." result = safe_list_sanitize(input) acceptable_results = [ # libxml2 = 2.9.14 %{&lt;![CDATA[<span>neverending...</span>}, # other libxml2 %{neverending...}, # xerces+neko "" ] assert_includes(acceptable_results, result) end def test_should_not_mangle_urls_with_ampersand assert_sanitized %{<a href=\"http://www.domain.com?var1=1&amp;var2=2\">my link</a>} end def test_should_sanitize_neverending_attribute # note that assert_dom_equal chokes in this case! so avoid using assert_sanitized assert_equal("<span class=\"\\\"></span>", safe_list_sanitize("<span class=\"\\\">")) end [ %(<a href="javascript&#x3a;alert('XSS');">), %(<a href="javascript&#x003a;alert('XSS');">), %(<a href="javascript&#x3A;alert('XSS');">), %(<a href="javascript&#x003A;alert('XSS');">) ].each_with_index do |enc_hack, i| define_method "test_x03a_handling_#{i + 1}" do assert_sanitized enc_hack, "<a></a>" end end def test_x03a_legitimate assert_sanitized %(<a href="http&#x3a;//legit">asdf</a>), %(<a href="http://legit">asdf</a>) assert_sanitized %(<a href="http&#x3A;//legit">asdf</a>), %(<a href="http://legit">asdf</a>) end def test_sanitize_ascii_8bit_string safe_list_sanitize("<div><a>hello</a></div>".encode("ASCII-8BIT")).tap do |sanitized| assert_equal "<div><a>hello</a></div>", sanitized assert_equal Encoding::UTF_8, sanitized.encoding end end def test_sanitize_data_attributes assert_sanitized %(<a href="/blah" data-method="post">foo</a>), %(<a href="/blah">foo</a>) assert_sanitized %(<a data-remote="true" data-type="script" data-method="get" data-cross-domain="true" href="attack.js">Launch the missiles</a>), %(<a href="attack.js">Launch the missiles</a>) end def test_allow_data_attribute_if_requested text = %(<a data-foo="foo">foo</a>) assert_equal %(<a data-foo="foo">foo</a>), safe_list_sanitize(text, attributes: ["data-foo"]) end # https://developer.mozilla.org/en-US/docs/Glossary/Void_element VOID_ELEMENTS = %w[area base br col embed hr img input keygen link meta param source track wbr] %w(strong em b i p code pre tt samp kbd var sub sup dfn cite big small address hr br div span h1 h2 h3 h4 h5 h6 ul ol li dl dt dd abbr acronym a img blockquote del ins time).each do |tag_name| define_method "test_default_safelist_should_allow_#{tag_name}" do if VOID_ELEMENTS.include?(tag_name) assert_sanitized("<#{tag_name}>") else assert_sanitized("<#{tag_name}>foo</#{tag_name}>") end end end def test_datetime_attribute assert_sanitized("<time datetime=\"2023-01-01\">Today</time>") end def test_abbr_attribute scope_allowed_tags(%w(table tr th td)) do assert_sanitized(%(<table><tr><td abbr="UK">United Kingdom</td></tr></table>)) end end def test_uri_escaping_of_href_attr_in_a_tag_in_safe_list_sanitizer html = %{<a href='examp<!--" unsafeattr=foo()>-->le.com'>test</a>} text = safe_list_sanitize(html) acceptable_results = [ # nokogiri's vendored+patched libxml2 (0002-Update-entities-to-remove-handling-of-ssi.patch) %{<a href="examp&lt;!--%22%20unsafeattr=foo()&gt;--&gt;le.com">test</a>}, # system libxml2 %{<a href="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>}, # xerces+neko %{<a href="examp&lt;!--%22 unsafeattr=foo()&gt;--&gt;le.com">test</a>} ] assert_includes(acceptable_results, text) end def test_uri_escaping_of_src_attr_in_a_tag_in_safe_list_sanitizer html = %{<a src='examp<!--" unsafeattr=foo()>-->le.com'>test</a>} text = safe_list_sanitize(html) acceptable_results = [ # nokogiri's vendored+patched libxml2 (0002-Update-entities-to-remove-handling-of-ssi.patch) %{<a src="examp&lt;!--%22%20unsafeattr=foo()&gt;--&gt;le.com">test</a>}, # system libxml2 %{<a src="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>}, # xerces+neko %{<a src="examp&lt;!--%22 unsafeattr=foo()&gt;--&gt;le.com">test</a>} ] assert_includes(acceptable_results, text) end def test_uri_escaping_of_name_attr_in_a_tag_in_safe_list_sanitizer html = %{<a name='examp<!--" unsafeattr=foo()>-->le.com'>test</a>} text = safe_list_sanitize(html) acceptable_results = [ # nokogiri's vendored+patched libxml2 (0002-Update-entities-to-remove-handling-of-ssi.patch) %{<a name="examp&lt;!--%22%20unsafeattr=foo()&gt;--&gt;le.com">test</a>}, # system libxml2 %{<a name="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>}, # xerces+neko %{<a name="examp&lt;!--%22 unsafeattr=foo()&gt;--&gt;le.com">test</a>} ] assert_includes(acceptable_results, text) end def test_uri_escaping_of_name_action_in_a_tag_in_safe_list_sanitizer html = %{<a action='examp<!--" unsafeattr=foo()>-->le.com'>test</a>} text = safe_list_sanitize(html, attributes: ["action"]) acceptable_results = [ # nokogiri's vendored+patched libxml2 (0002-Update-entities-to-remove-handling-of-ssi.patch) %{<a action="examp&lt;!--%22%20unsafeattr=foo()&gt;--&gt;le.com">test</a>}, # system libxml2 %{<a action="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>}, # xerces+neko %{<a action="examp&lt;!--%22 unsafeattr=foo()&gt;--&gt;le.com">test</a>}, ] assert_includes(acceptable_results, text) end def test_exclude_node_type_processing_instructions input = "<div>text</div><?div content><b>text</b>" result = safe_list_sanitize(input) acceptable_results = [ # jruby cyberneko (nokogiri < 1.14.0) "<div>text</div>", # everything else "<div>text</div><b>text</b>", ] assert_includes(acceptable_results, result) end def test_exclude_node_type_comment assert_equal("<div>text</div><b>text</b>", safe_list_sanitize("<div>text</div><!-- comment --><b>text</b>")) end %w[text/plain text/css image/png image/gif image/jpeg].each do |mediatype| define_method "test_mediatype_#{mediatype}_allowed" do input = %Q(<img src="data:#{mediatype};base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">) expected = input actual = safe_list_sanitize(input) assert_equal(expected, actual) input = %Q(<img src="DATA:#{mediatype};base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">) expected = input actual = safe_list_sanitize(input) assert_equal(expected, actual) end end def test_mediatype_text_html_disallowed input = '<img src="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">' expected = "<img>" actual = safe_list_sanitize(input) assert_equal(expected, actual) input = '<img src="DATA:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">' expected = "<img>" actual = safe_list_sanitize(input) assert_equal(expected, actual) end def test_mediatype_image_svg_xml_disallowed input = '<img src="data:image/svg+xml;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">' expected = "<img>" actual = safe_list_sanitize(input) assert_equal(expected, actual) input = '<img src="DATA:image/svg+xml;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">' expected = "<img>" actual = safe_list_sanitize(input) assert_equal(expected, actual) end def test_mediatype_other_disallowed input = '<a href="data:foo;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">foo</a>' expected = "<a>foo</a>" actual = safe_list_sanitize(input) assert_equal(expected, actual) input = '<a href="DATA:foo;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">foo</a>' expected = "<a>foo</a>" actual = safe_list_sanitize(input) assert_equal(expected, actual) end def test_scrubbing_svg_attr_values_that_allow_ref input = '<div fill="yellow url(http://bad.com/) #fff">hey</div>' expected = '<div fill="yellow #fff">hey</div>' actual = scope_allowed_attributes %w(fill) do safe_list_sanitize(input) end assert_equal(expected, actual) end def test_style_with_css_payload input, tags = "<style>div > span { background: \"red\"; }</style>", ["style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<style>div &gt; span { background: \"red\"; }</style>", # libgumbo "<style>div > span { background: \"red\"; }</style>", ] assert_includes(acceptable_results, actual) end def test_combination_of_select_and_style_with_css_payload input, tags = "<select><style>div > span { background: \"red\"; }</style></select>", ["select", "style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<select><style>div &gt; span { background: \"red\"; }</style></select>", # libgumbo "<select>div &gt; span { background: \"red\"; }</select>", ] assert_includes(acceptable_results, actual) end def test_combination_of_select_and_style_with_script_payload input, tags = "<select><style><script>alert(1)</script></style></select>", ["select", "style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<select><style>&lt;script&gt;alert(1)&lt;/script&gt;</style></select>", # libgumbo "<select>alert(1)</select>", ] assert_includes(acceptable_results, actual) end def test_combination_of_svg_and_style_with_script_payload input, tags = "<svg><style><script>alert(1)</script></style></svg>", ["svg", "style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<svg><style>&lt;script&gt;alert(1)&lt;/script&gt;</style></svg>", # libgumbo "<svg><style></style></svg>", ] assert_includes(acceptable_results, actual) end def test_combination_of_math_and_style_with_img_payload input, tags = "<math><style><img src=x onerror=alert(1)></style></math>", ["math", "style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<math><style>&lt;img src=x onerror=alert(1)&gt;</style></math>", # libgumbo "<math><style></style></math>", ] assert_includes(acceptable_results, actual) end def test_combination_of_math_and_style_with_img_payload_2 input, tags = "<math><style><img src=x onerror=alert(1)></style></math>", ["math", "style", "img"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<math><style>&lt;img src=x onerror=alert(1)&gt;</style></math>", # libgumbo "<math><style></style></math><img src=\"x\">", ] assert_includes(acceptable_results, actual) end def test_combination_of_svg_and_style_with_img_payload input, tags = "<svg><style><img src=x onerror=alert(1)></style></svg>", ["svg", "style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<svg><style>&lt;img src=x onerror=alert(1)&gt;</style></svg>", # libgumbo "<svg><style></style></svg>", ] assert_includes(acceptable_results, actual) end def test_combination_of_svg_and_style_with_img_payload_2 input, tags = "<svg><style><img src=x onerror=alert(1)></style></svg>", ["svg", "style", "img"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<svg><style>&lt;img src=x onerror=alert(1)&gt;</style></svg>", # libgumbo "<svg><style></style></svg><img src=\"x\">", ] assert_includes(acceptable_results, actual) end def test_combination_of_svg_and_style_with_escaped_img_payload # https://hackerone.com/reports/2503220 input, tags = "<svg><style>&lt;img src onerror=alert(1)>", ["svg", "style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<svg><style>&amp;lt;img src onerror=alert(1)&gt;</style></svg>", # libgumbo "<svg><style>&lt;img src onerror=alert(1)&gt;</style></svg>", ] assert_includes(acceptable_results, actual) end def test_combination_of_math_and_style_with_escaped_img_payload # https://hackerone.com/reports/2503220 input, tags = "<math><style>&lt;img src onerror=alert(1)>", ["math", "style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<math><style>&amp;lt;img src onerror=alert(1)&gt;</style></math>", # libgumbo "<math><style>&lt;img src onerror=alert(1)&gt;</style></math>", ] assert_includes(acceptable_results, actual) end def test_combination_of_style_and_disallowed_svg_with_script_payload # https://hackerone.com/reports/2519936 input, tags = "<svg><style><style class='</style><script>alert(1)</script>'>", ["style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<style>&lt;style class='</style>alert(1)'&gt;", # libgumbo "", ] assert_includes(acceptable_results, actual) end def test_combination_of_style_and_disallowed_math_with_script_payload # https://hackerone.com/reports/2519936 input, tags = "<math><style><style class='</style><script>alert(1)</script>'>", ["style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<style>&lt;style class='</style>alert(1)'&gt;", # libgumbo "", ] assert_includes(acceptable_results, actual) end def test_math_with_disallowed_mtext_and_img_payload # https://hackerone.com/reports/2519941 input, tags = "<math><mtext><table><mglyph><style><img src=: onerror=alert(1)>", ["math", "style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<math><style>&lt;img src=: onerror=alert(1)&gt;</style></math>", # libgumbo "<math></math>", ] assert_includes(acceptable_results, actual) end def test_should_sanitize_illegal_style_properties raw = %(display:block; position:absolute; left:0; top:0; width:100%; height:100%; z-index:1; background-color:black; background-image:url(http://www.ragingplatypus.com/i/cam-full.jpg); background-x:center; background-y:center; background-repeat:repeat;) expected = %(display:block;width:100%;height:100%;background-color:black;background-x:center;background-y:center;) assert_equal expected, sanitize_css(raw) end def test_should_sanitize_with_trailing_space raw = "display:block; " expected = "display:block;" assert_equal expected, sanitize_css(raw) end def test_should_sanitize_xul_style_attributes raw = %(-moz-binding:url('http://ha.ckers.org/xssmoz.xml#xss')) assert_equal "", sanitize_css(raw) end def test_should_sanitize_div_background_image_unicode_encoded [ convert_to_css_hex("url(javascript:alert(1))", false), convert_to_css_hex("url(javascript:alert(1))", true), convert_to_css_hex("url(https://example.com)", false), convert_to_css_hex("url(https://example.com)", true), ].each do |propval| raw = "background-image:" + propval assert_empty(sanitize_css(raw)) end end def test_should_allow_div_background_image_unicode_encoded_safe_functions [ convert_to_css_hex("rgb(255,0,0)", false), convert_to_css_hex("rgb(255,0,0)", true), ].each do |propval| raw = "background-image:" + propval assert_includes(sanitize_css(raw), "background-image") end end def test_should_sanitize_div_style_expression raw = %(width: expression(alert('XSS'));) assert_equal "", sanitize_css(raw) end def test_should_sanitize_across_newlines raw = %(\nwidth:\nexpression(alert('XSS'));\n) assert_equal "", sanitize_css(raw) end def test_should_prune_mglyph # https://hackerone.com/reports/2519936 input = "<math><mtext><table><mglyph><style><img src=: onerror=alert(1)>" tags = %w(math mtext table mglyph style).freeze actual = nil assert_output(nil, /WARNING: 'mglyph' tags cannot be allowed by the PermitScrubber/) do actual = safe_list_sanitize(input, tags: tags) end acceptable_results = [ # libxml2 "<math><mtext><table><style>&lt;img src=: onerror=alert(1)&gt;</style></table></mtext></math>", # libgumbo "<math><mtext><style><img src=: onerror=alert(1)></style><table></table></mtext></math>", ] assert_includes(acceptable_results, actual) end def test_should_prune_malignmark # https://hackerone.com/reports/2519936 input = "<math><mtext><table><malignmark><style><img src=: onerror=alert(1)>" tags = %w(math mtext table malignmark style).freeze actual = nil assert_output(nil, /WARNING: 'malignmark' tags cannot be allowed by the PermitScrubber/) do actual = safe_list_sanitize(input, tags: tags) end acceptable_results = [ # libxml2 "<math><mtext><table><style>&lt;img src=: onerror=alert(1)&gt;</style></table></mtext></math>", # libgumbo "<math><mtext><style><img src=: onerror=alert(1)></style><table></table></mtext></math>", ] assert_includes(acceptable_results, actual) end def test_should_prune_noscript # https://hackerone.com/reports/2509647 input = "<div><noscript><p id='</noscript><script>alert(1)</script>'></noscript>" tags = ["p", "div", "noscript"].freeze actual = nil assert_output(nil, /WARNING: 'noscript' tags cannot be allowed by the PermitScrubber/) do actual = safe_list_sanitize(input, tags: tags, attributes: %w(id)) end acceptable_results = [ # libxml2 "<div><p id=\"&lt;/noscript&gt;&lt;script&gt;alert(1)&lt;/script&gt;\"></p></div>", # libgumbo "<div><p id=\"</noscript><script>alert(1)</script>\"></p></div>", ] assert_includes(acceptable_results, actual) end protected def safe_list_sanitize(input, options = {}) module_under_test::SafeListSanitizer.new.sanitize(input, options) end def assert_sanitized(input, expected = nil) assert_equal((expected || input), safe_list_sanitize(input)) end def scope_allowed_tags(tags) old_tags = module_under_test::SafeListSanitizer.allowed_tags module_under_test::SafeListSanitizer.allowed_tags = tags yield module_under_test::SafeListSanitizer.new ensure module_under_test::SafeListSanitizer.allowed_tags = old_tags end def scope_allowed_attributes(attributes) old_attributes = module_under_test::SafeListSanitizer.allowed_attributes module_under_test::SafeListSanitizer.allowed_attributes = attributes yield module_under_test::SafeListSanitizer.new ensure module_under_test::SafeListSanitizer.allowed_attributes = old_attributes end def sanitize_css(input) module_under_test::SafeListSanitizer.new.sanitize_css(input) end # note that this is used for testing CSS hex encoding: \\[0-9a-f]{1,6} def convert_to_css_hex(string, escape_parens = false) string.chars.map do |c| if !escape_parens && (c == "(" || c == ")") c else format('\00%02X', c.ord) end end.join end end class HTML4SafeListSanitizerTest < Minitest::Test @module_under_test = Rails::HTML4 include SafeListSanitizerTest end class HTML5SafeListSanitizerTest < Minitest::Test @module_under_test = Rails::HTML5 include SafeListSanitizerTest def test_should_not_be_vulnerable_to_nokogiri_foreign_style_serialization_bug # https://hackerone.com/reports/2503220 input = "<svg><style>&lt;img src onerror=alert(1)>" result = Rails::HTML5::SafeListSanitizer.new.sanitize(input, tags: ["svg", "style"]) browser = Nokogiri::HTML5::Document.parse(result) xss = browser.at_xpath("//img/@onerror") assert_nil(xss) end def test_should_not_be_vulnerable_to_ns_confusion_2519936 # https://hackerone.com/reports/2519936 input = "<math><style><style class='</style><script>alert(1)</script>'>" result = Rails::HTML5::SafeListSanitizer.new.sanitize(input, tags: ["style"]) browser = Nokogiri::HTML5::Document.parse(result) xss = browser.at_xpath("//script") assert_nil(xss) end def test_should_not_be_vulnerable_to_ns_confusion_2519941 # https://hackerone.com/reports/2519941 input = "<math><mtext><table><mglyph><style><img src=: onerror=alert(1)>" result = Rails::HTML5::SafeListSanitizer.new.sanitize(input, tags: %w(math style)) browser = Nokogiri::HTML5::Document.parse(result) xss = browser.at_xpath("//img/@onerror") assert_nil(xss) end def test_should_not_be_vulnerable_to_mglyph_namespace_confusion # https://hackerone.com/reports/2519936 input = "<math><mtext><table><mglyph><style><img src=: onerror=alert(1)>" tags = %w(math mtext table mglyph style) result = nil assert_output(nil, /WARNING/) do result = safe_list_sanitize(input, tags: tags) end browser = Nokogiri::HTML5::Document.parse(result) xss = browser.at_xpath("//img/@onerror") assert_nil(xss) end def test_should_not_be_vulnerable_to_malignmark_namespace_confusion # https://hackerone.com/reports/2519936 input = "<math><mtext><table><malignmark><style><img src=: onerror=alert(1)>" tags = %w(math mtext table malignmark style) result = nil assert_output(nil, /WARNING/) do result = safe_list_sanitize(input, tags: tags) end browser = Nokogiri::HTML5::Document.parse(result) xss = browser.at_xpath("//img/@onerror") assert_nil(xss) end def test_should_not_be_vulnerable_to_noscript_attacks # https://hackerone.com/reports/2509647 skip("browser assertion requires parse_noscript_content_as_text") unless Nokogiri::VERSION >= "1.17" input = '<noscript><p id="</noscript><script>alert(1)</script>"></noscript>' result = nil assert_output(nil, /WARNING/) do result = Rails::HTML5::SafeListSanitizer.new.sanitize(input, tags: %w(p div noscript), attributes: %w(id class style)) end browser = Nokogiri::HTML5::Document.parse(result, parse_noscript_content_as_text: true) xss = browser.at_xpath("//script") assert_nil(xss) end end if loofah_html5_support? end ��������������������������������������������������������������������������������������������������������������������������������������rails-rails-html-sanitizer-a8a0413/test/scrubbers_test.rb�������������������������������������������0000664�0000000�0000000�00000017662�15147370663�0023614�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true require_relative "test_helper" class ScrubberTest < Minitest::Test protected def scrub_fragment(html) Loofah.scrub_fragment(html, @scrubber).to_s end def assert_scrubbed(html, expected = html) output = scrub_fragment(html) assert_equal expected, output end def to_node(text) Loofah.fragment(text).children.first end def assert_node_skipped(text) assert_scrub_returns(Loofah::Scrubber::CONTINUE, text) end def assert_scrub_stopped(text) assert_scrub_returns(Loofah::Scrubber::STOP, text) end def assert_scrub_returns(return_value, text) node = to_node(text) assert_equal return_value, @scrubber.scrub(node) end end class PermitScrubberTest < ScrubberTest def setup @scrubber = Rails::HTML::PermitScrubber.new end def test_responds_to_scrub assert @scrubber.respond_to?(:scrub) end def test_default_scrub_behavior assert_scrubbed "<tag>hello</tag>", "hello" end def test_default_scrub_removes_comments assert_scrubbed("<div>one</div><!-- two --><span>three</span>", "<div>one</div><span>three</span>") end def test_default_scrub_removes_processing_instructions input = "<div>one</div><?div two><span>three</span>" result = scrub_fragment(input) acceptable_results = [ # jruby cyberneko (nokogiri < 1.14.0) "<div>one</div>", # everything else "<div>one</div><span>three</span>", ] assert_includes(acceptable_results, result) end def test_default_attributes_removal_behavior assert_scrubbed '<p cooler="hello">hello</p>', "<p>hello</p>" end def test_leaves_supplied_tags @scrubber.tags = %w(a) assert_scrubbed "<a>hello</a>" end def test_leaves_only_supplied_tags html = "<tag>leave me <span>now</span></tag>" @scrubber.tags = %w(tag) assert_scrubbed html, "<tag>leave me now</tag>" end def test_prunes_tags @scrubber = Rails::HTML::PermitScrubber.new(prune: true) @scrubber.tags = %w(tag) html = "<tag>leave me <span>now</span></tag>" assert_scrubbed html, "<tag>leave me </tag>" end def test_leaves_comments_when_supplied_as_tag @scrubber.tags = %w(div comment) assert_scrubbed("<div>one</div><!-- two --><span>three</span>", "<div>one</div><!-- two -->three") end def test_leaves_only_supplied_tags_nested html = "<tag>leave <em>me <span>now</span></em></tag>" @scrubber.tags = %w(tag) assert_scrubbed html, "<tag>leave me now</tag>" end def test_leaves_supplied_attributes @scrubber.attributes = %w(cooler) assert_scrubbed '<a cooler="hello"></a>' end def test_leaves_only_supplied_attributes @scrubber.attributes = %w(cooler) assert_scrubbed '<a cooler="hello" b="c" d="e"></a>', '<a cooler="hello"></a>' end def test_leaves_supplied_tags_and_attributes @scrubber.tags = %w(tag) @scrubber.attributes = %w(cooler) assert_scrubbed '<tag cooler="hello"></tag>' end def test_leaves_only_supplied_tags_and_attributes @scrubber.tags = %w(tag) @scrubber.attributes = %w(cooler) html = '<a></a><tag href=""></tag><tag cooler=""></tag>' assert_scrubbed html, '<tag></tag><tag cooler=""></tag>' end def test_does_not_allow_safelisted_mglyph # https://hackerone.com/reports/2519936 assert_output(nil, /WARNING: 'mglyph' tags cannot be allowed by the PermitScrubber/) do @scrubber.tags = ["div", "mglyph", "span"] end assert_equal(["div", "span"], @scrubber.tags) end def test_does_not_allow_safelisted_malignmark # https://hackerone.com/reports/2519936 assert_output(nil, /WARNING: 'malignmark' tags cannot be allowed by the PermitScrubber/) do @scrubber.tags = ["div", "malignmark", "span"] end assert_equal(["div", "span"], @scrubber.tags) end def test_does_not_allow_safelisted_noscript # https://hackerone.com/reports/2509647 assert_output(nil, /WARNING: 'noscript' tags cannot be allowed by the PermitScrubber/) do @scrubber.tags = ["div", "noscript", "span"] end assert_equal(["div", "span"], @scrubber.tags) end def test_leaves_text assert_scrubbed("some text") end def test_skips_text_nodes assert_node_skipped("some text") end def test_tags_accessor_validation e = assert_raises(ArgumentError) do @scrubber.tags = "tag" end assert_equal "You should pass :tags as an Enumerable", e.message assert_nil @scrubber.tags, "Tags should be nil when validation fails" end def test_attributes_accessor_validation e = assert_raises(ArgumentError) do @scrubber.attributes = "cooler" end assert_equal "You should pass :attributes as an Enumerable", e.message assert_nil @scrubber.attributes, "Attributes should be nil when validation fails" end end class TargetScrubberTest < ScrubberTest def setup @scrubber = Rails::HTML::TargetScrubber.new end def test_targeting_tags_removes_only_them @scrubber.tags = %w(a h1) html = "<script></script><a></a><h1></h1>" assert_scrubbed html, "<script></script>" end def test_targeting_tags_removes_only_them_nested @scrubber.tags = %w(a) html = "<tag><a><tag><a></a></tag></a></tag>" assert_scrubbed html, "<tag><tag></tag></tag>" end def test_targeting_attributes_removes_only_them @scrubber.attributes = %w(class id) html = '<a class="a" id="b" onclick="c"></a>' assert_scrubbed html, '<a onclick="c"></a>' end def test_targeting_tags_and_attributes_removes_only_them @scrubber.tags = %w(tag) @scrubber.attributes = %w(remove) html = '<tag remove="" other=""></tag><a remove="" other=""></a>' assert_scrubbed html, '<a other=""></a>' end def test_prunes_tags @scrubber = Rails::HTML::TargetScrubber.new(prune: true) @scrubber.tags = %w(span) html = "<tag>leave me <span>now</span></tag>" assert_scrubbed html, "<tag>leave me </tag>" end end class TextOnlyScrubberTest < ScrubberTest def setup @scrubber = Rails::HTML::TextOnlyScrubber.new end def test_removes_all_tags_and_keep_the_content assert_scrubbed "<tag>hello</tag>", "hello" end def test_skips_text_nodes assert_node_skipped("some text") end end class ReturningStopFromScrubNodeTest < ScrubberTest class ScrubStopper < Rails::HTML::PermitScrubber def scrub_node(node) Loofah::Scrubber::STOP end end class ScrubContinuer < Rails::HTML::PermitScrubber def scrub_node(node) Loofah::Scrubber::CONTINUE end end def test_returns_stop_from_scrub_if_scrub_node_does @scrubber = ScrubStopper.new assert_scrub_stopped "<script>remove me</script>" end def test_returns_continue_from_scrub_if_scrub_node_does @scrubber = ScrubContinuer.new assert_node_skipped "<script>keep me</script>" end end class PermitScrubberMinimalOperationsTest < ScrubberTest class TestPermitScrubber < Rails::HTML::PermitScrubber def initialize @scrub_attribute_args = [] @scrub_attributes_args = [] super self.tags = ["div"] self.attributes = ["class"] end def scrub_attributes(node) @scrub_attributes_args << node.name super end def scrub_attribute(node, attr) @scrub_attribute_args << [node.name, attr.name] super end end def test_does_not_scrub_removed_attributes @scrubber = TestPermitScrubber.new input = "<div class='foo' href='bar'></div>" frag = scrub_fragment(input) assert_equal("<div class=\"foo\"></div>", frag) assert_equal([["div", "class"]], @scrubber.instance_variable_get(:@scrub_attribute_args)) end def test_does_not_scrub_attributes_of_a_removed_node @scrubber = TestPermitScrubber.new input = "<div class='foo' href='bar'><svg xlink:href='asdf'><set></set></svg></div>" frag = scrub_fragment(input) assert_equal("<div class=\"foo\"></div>", frag) assert_equal(["div"], @scrubber.instance_variable_get(:@scrub_attributes_args)) end end ������������������������������������������������������������������������������rails-rails-html-sanitizer-a8a0413/test/test_helper.rb����������������������������������������������0000664�0000000�0000000�00000000321�15147370663�0023061�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true require "minitest/autorun" require "rails-html-sanitizer" puts "nokogiri version info: #{Nokogiri::VERSION_INFO}" puts "html5 support: #{Rails::HTML::Sanitizer.html5_support?}" �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������