aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_view/asset_paths.rb
blob: 9c1c821f8f6b0177cbf109f1ffc64dce5d40ba16 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
require 'zlib'
require 'active_support/core_ext/file'
require 'action_controller/metal/exceptions'

module ActionView
  class AssetPaths #:nodoc:
    attr_reader :config, :controller

    def initialize(config, controller = nil)
      @config = config
      @controller = controller
    end

    # Add the extension +ext+ if not present. Return full or scheme-relative URLs otherwise untouched.
    # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
    # roots. Rewrite the asset path for cache-busting asset ids. Include
    # asset host, if configured, with the correct request protocol.
    #
    # When include_host is true and the asset host does not specify the protocol
    # the protocol parameter specifies how the protocol will be added.
    # When :relative (default), the protocol will be determined by the client using current protocol
    # When :request, the protocol will be the request protocol
    # Otherwise, the protocol is used (E.g. :http, :https, etc)
    def compute_public_path(source, dir, ext = nil, include_host = true, protocol = nil)
      source = source.to_s
      return source if is_uri?(source)

      source = rewrite_extension(source, dir, ext) if ext
      source = rewrite_asset_path(source, dir)
      source = rewrite_relative_url_root(source, relative_url_root)
      source = rewrite_host_and_protocol(source, protocol) if include_host
      source
    end

    # Return the filesystem path for the source
    def compute_source_path(source, dir, ext)
      source = rewrite_extension(source, dir, ext) if ext
      File.join(config.assets_dir, dir, source)
    end

    def is_uri?(path)
      path =~ %r{^[-a-z]+://|^cid:|^//}
    end

  private

    def rewrite_extension(source, dir, ext)
      raise NotImplementedError
    end

    def rewrite_asset_path(source, path = nil)
      raise NotImplementedError
    end

    def rewrite_relative_url_root(source, relative_url_root)
      relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source
    end

    def has_request?
      controller.respond_to?(:request)
    end

    def rewrite_host_and_protocol(source, protocol = nil)
      host = compute_asset_host(source)
      if host && !is_uri?(host)
        if (protocol || default_protocol) == :request && !has_request?
          host = nil
        else
          host = "#{compute_protocol(protocol)}#{host}"
        end
      end
      host ? "#{host}#{source}" : source
    end

    def compute_protocol(protocol)
      protocol ||= default_protocol
      case protocol
      when :relative
        "//"
      when :request
        unless @controller
          invalid_asset_host!("The protocol requested was :request. Consider using :relative instead.")
        end
        @controller.request.protocol
      else
        "#{protocol}://"
      end
    end

    def default_protocol
      protocol = @config.action_controller.default_asset_host_protocol if @config.action_controller.present?
      protocol ||= @config.default_asset_host_protocol
      protocol || (has_request? ? :request : :relative)
    end

    def invalid_asset_host!(help_message)
      raise ActionController::RoutingError, "This asset host cannot be computed without a request in scope. #{help_message}"
    end

    # Pick an asset host for this source. Returns +nil+ if no host is set,
    # the host if no wildcard is set, the host interpolated with the
    # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
    # or the value returned from invoking call on an object responding to call
    # (proc or otherwise).
    def compute_asset_host(source)
      if host = asset_host_config
        if host.respond_to?(:call)
          args = [source]
          arity = arity_of(host)
          if arity > 1 && !has_request?
            invalid_asset_host!("Remove the second argument to your asset_host Proc if you do not need the request.")
          end
          args << current_request if (arity > 1 || arity < 0) && has_request?
          host.call(*args)
        else
          (host =~ /%d/) ? host % (Zlib.crc32(source) % 4) : host
        end
      end
    end

    def relative_url_root
      config = controller.config if controller.respond_to?(:config)
      config ||= config.action_controller if config.action_controller.present?
      config.relative_url_root
    end

    def asset_host_config
      if config.action_controller.present?
        config.action_controller.asset_host
      else
        config.asset_host
      end
    end

    # Returns the current request if one exists.
    def current_request
      controller.request if has_request?
    end

    # Returns the arity of a callable
    def arity_of(callable)
      callable.respond_to?(:arity) ? callable.arity : callable.method(:call).arity
    end

  end
end