Module: Senren::Rails::BaseComponentPatch

Defined in:
lib/senren/rails/base_component_patch.rb

Overview

Ruby appended to a host app's existing BaseComponent when it predates the URL-aware component templates.

This duplicates the helpers in lib/generators/senren/install/templates/base_component.rb.tt, because apps installed before those helpers existed never receive the template again. The two definitions are pinned together by test/security/component_url_security_test.rb: if they drift, migrated apps silently keep an older, weaker safe_url.

Constant Summary collapse

URL_HELPERS =

Single-quoted heredoc: the body is emitted verbatim, so the backslash escapes inside safe_url survive into the host app's file.

<<~'RUBY'

  # Added by senren:add for compatibility with URL-aware component templates.
  require 'uri'

  module Senren
    class BaseComponent
      SAFE_URL_PROTOCOLS = %w[http https mailto tel].freeze unless const_defined?(:SAFE_URL_PROTOCOLS)
      SAFE_MEDIA_URL_PROTOCOLS = %w[http https].freeze unless const_defined?(:SAFE_MEDIA_URL_PROTOCOLS)

      private

      def safe_url(value, fallback: '#', protocols: SAFE_URL_PROTOCOLS)
        url = value.to_s.strip
        return fallback if url.empty?
        # Browsers treat "\" as "/" for special schemes and strip TAB/CR/LF
        # before parsing, so "/\evil.example" and "/<TAB>/evil.example" would
        # both slip past a plain "//" check and resolve off-origin.
        return fallback if url.include?('\\')
        return fallback if url.match?(/[[:cntrl:]]/)
        return url if url.start_with?('#')
        # Any leading "//" is protocol-relative regardless of how many slashes
        # follow. Rejecting here rather than relying on URI.parse matters:
        # URI.parse("///evil.example") reports no scheme and no host, so the
        # scheme-less fallback below would otherwise hand back a URL the browser
        # resolves to https://evil.example/.
        return fallback if url.start_with?('//')
        return url if url.start_with?('/')

        uri = URI.parse(url)
        return url if uri.scheme && Array(protocols).map(&:to_s).include?(uri.scheme.downcase)
        return fallback if uri.host
        return url unless uri.scheme

        fallback
      rescue URI::InvalidURIError
        fallback
      end

      def safe_media_url(value, fallback: nil)
        safe_url(value, fallback: fallback, protocols: SAFE_MEDIA_URL_PROTOCOLS)
      end
    end
  end
RUBY