This file is indexed.

/usr/lib/ruby/vendor_ruby/rack/cache/context.rb is in ruby-rack-cache 1.2-4.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
require 'rack/cache/options'
require 'rack/cache/request'
require 'rack/cache/response'
require 'rack/cache/storage'

module Rack::Cache
  # Implements Rack's middleware interface and provides the context for all
  # cache logic, including the core logic engine.
  class Context
    include Rack::Cache::Options

    # Array of trace Symbols
    attr_reader :trace

    # The Rack application object immediately downstream.
    attr_reader :backend

    def initialize(backend, options={})
      @backend = backend
      @trace = []
      @env = nil

      initialize_options options
      yield self if block_given?

      @private_header_keys =
        private_headers.map { |name| "HTTP_#{name.upcase.tr('-', '_')}" }
    end

    # The configured MetaStore instance. Changing the rack-cache.metastore
    # value effects the result of this method immediately.
    def metastore
      uri = options['rack-cache.metastore']
      storage.resolve_metastore_uri(uri)
    end

    # The configured EntityStore instance. Changing the rack-cache.entitystore
    # value effects the result of this method immediately.
    def entitystore
      uri = options['rack-cache.entitystore']
      storage.resolve_entitystore_uri(uri)
    end

    # The Rack call interface. The receiver acts as a prototype and runs
    # each request in a dup object unless the +rack.run_once+ variable is
    # set in the environment.
    def call(env)
      if env['rack.run_once']
        call! env
      else
        clone.call! env
      end
    end

    # The real Rack call interface. The caching logic is performed within
    # the context of the receiver.
    def call!(env)
      @trace = []
      @default_options.each { |k,v| env[k] ||= v }
      @env = env
      @request = Request.new(@env.dup.freeze)

      response =
        if @request.get? || @request.head?
          if !@env['HTTP_EXPECT'] && !@env['rack-cache.force-pass']
            lookup
          else
            pass
          end
        else
          invalidate
        end

      # log trace and set X-Rack-Cache tracing header
      trace = @trace.join(', ')
      response.headers['X-Rack-Cache'] = trace

      # write log message to rack.errors
      if verbose?
        message = "cache: [%s %s] %s\n" %
          [@request.request_method, @request.fullpath, trace]
        @env['rack.errors'].write(message)
      end

      # tidy up response a bit
      if (@request.get? || @request.head?) && not_modified?(response)
        response.not_modified!
      end

      if @request.head?
        response.body.close if response.body.respond_to?(:close)
        response.body = []
      end
      response.to_a
    end

  private

    # Record that an event took place.
    def record(event)
      @trace << event
    end

    # Does the request include authorization or other sensitive information
    # that should cause the response to be considered private by default?
    # Private responses are not stored in the cache.
    def private_request?
      @private_header_keys.any? { |key| @env.key?(key) }
    end

    # Determine if the #response validators (ETag, Last-Modified) matches
    # a conditional value specified in #request.
    def not_modified?(response)
      last_modified = @request.env['HTTP_IF_MODIFIED_SINCE']
      if etags = @request.env['HTTP_IF_NONE_MATCH']
        etags = etags.split(/\s*,\s*/)
        (etags.include?(response.etag) || etags.include?('*')) && (!last_modified || response.last_modified == last_modified)
      elsif last_modified
        response.last_modified == last_modified
      end
    end

    # Whether the cache entry is "fresh enough" to satisfy the request.
    def fresh_enough?(entry)
      if entry.fresh?
        if allow_revalidate? && max_age = @request.cache_control.max_age
          max_age > 0 && max_age >= entry.age
        else
          true
        end
      end
    end

    # Delegate the request to the backend and create the response.
    def forward
      Response.new(*backend.call(@env))
    end

    # The request is sent to the backend, and the backend's response is sent
    # to the client, but is not entered into the cache.
    def pass
      record :pass
      forward
    end

    # Invalidate POST, PUT, DELETE and all methods not understood by this cache
    # See RFC2616 13.10
    def invalidate
      metastore.invalidate(@request, entitystore)
    rescue Exception => e
      log_error(e)
      pass
    else
      record :invalidate
      pass
    end

    # Try to serve the response from cache. When a matching cache entry is
    # found and is fresh, use it as the response without forwarding any
    # request to the backend. When a matching cache entry is found but is
    # stale, attempt to #validate the entry with the backend using conditional
    # GET. When no matching cache entry is found, trigger #miss processing.
    def lookup
      if @request.no_cache? && allow_reload?
        record :reload
        fetch
      else
        begin
          entry = metastore.lookup(@request, entitystore)
        rescue Exception => e
          log_error(e)
          return pass
        end
        if entry
          if fresh_enough?(entry)
            record :fresh
            entry.headers['Age'] = entry.age.to_s
            entry
          else
            record :stale
            validate(entry)
          end
        else
          record :miss
          fetch
        end
      end
    end

    # Validate that the cache entry is fresh. The original request is used
    # as a template for a conditional GET request with the backend.
    def validate(entry)
      # send no head requests because we want content
      @env['REQUEST_METHOD'] = 'GET'

      # add our cached last-modified validator to the environment
      @env['HTTP_IF_MODIFIED_SINCE'] = entry.last_modified

      # Add our cached etag validator to the environment.
      # We keep the etags from the client to handle the case when the client
      # has a different private valid entry which is not cached here.
      cached_etags = entry.etag.to_s.split(/\s*,\s*/)
      request_etags = @request.env['HTTP_IF_NONE_MATCH'].to_s.split(/\s*,\s*/)
      etags = (cached_etags + request_etags).uniq
      @env['HTTP_IF_NONE_MATCH'] = etags.empty? ? nil : etags.join(', ')

      response = forward

      if response.status == 304
        record :valid

        # Check if the response validated which is not cached here
        etag = response.headers['ETag']
        return response if etag && request_etags.include?(etag) && !cached_etags.include?(etag)

        entry = entry.dup
        entry.headers.delete('Date')
        %w[Date Expires Cache-Control ETag Last-Modified].each do |name|
          next unless value = response.headers[name]
          entry.headers[name] = value
        end

        # even though it's empty, be sure to close the response body from upstream
        # because middleware use close to signal end of response
        response.body.close if response.body.respond_to?(:close)

        response = entry
      else
        record :invalid
      end

      store(response) if response.cacheable?

      response
    end

    # The cache missed or a reload is required. Forward the request to the
    # backend and determine whether the response should be stored. This allows
    # conditional / validation requests through to the backend but performs no
    # caching of the response when the backend returns a 304.
    def fetch
      # send no head requests because we want content
      @env['REQUEST_METHOD'] = 'GET'

      response = forward

      # Mark the response as explicitly private if any of the private
      # request headers are present and the response was not explicitly
      # declared public.
      if private_request? && !response.cache_control.public?
        response.private = true
      elsif default_ttl > 0 && response.ttl.nil? && !response.cache_control.must_revalidate?
        # assign a default TTL for the cache entry if none was specified in
        # the response; the must-revalidate cache control directive disables
        # default ttl assigment.
        response.ttl = default_ttl
      end

      store(response) if response.cacheable?

      response
    end

    # Write the response to the cache.
    def store(response)
      strip_ignore_headers(response)
      metastore.store(@request, response, entitystore)
      response.headers['Age'] = response.age.to_s
    rescue Exception => e
      log_error(e)
      nil
    else
      record :store
    end

    # Remove all ignored response headers before writing to the cache.
    def strip_ignore_headers(response)
      stripped_values = ignore_headers.map { |name| response.headers.delete(name) }
      record :ignore if stripped_values.any?
    end

    def log_error(exception)
      @env['rack.errors'].write("cache error: #{exception.message}\n#{exception.backtrace.join("\n")}\n")
    end
  end
end