class Rack::TailFile

Rack::File serves files below the root directory given, according to the path info of the Rack request. e.g. when Rack::File.new(“/etc”) is used, you can access 'passwd' file as localhost:9292/passwd

Handlers can detect if bodies are a Rack::File, and use mechanisms like sendfile on the path.

Constants

ALLOWED_VERBS
F
SEPS
VERSION

Attributes

cache_control[RW]
path[RW]
root[RW]
to_path[RW]

Public Class Methods

new(root, headers={}, default_mime = 'text/plain') click to toggle source
# File lib/rack/tail_file.rb, line 28
def initialize(root, headers={}, default_mime = 'text/plain')
  @root = root
  @headers = headers
  @default_mime = default_mime
end

Public Instance Methods

_call(env) click to toggle source
# File lib/rack/tail_file.rb, line 40
def _call(env)
  return fail(405, "Method Not Allowed") unless method_allowed?(env)
  return fail(403, "Forbidden") unless path_is_within_root?(env)

  @path = file_path(env)

  if available?
    serving(env)
  else
    fail(404, "File not found: #{path_info_for(env)}")
  end
end
available?() click to toggle source
# File lib/rack/tail_file.rb, line 57
def available?
  begin
    F.file?(@path) && F.readable?(@path)
  rescue SystemCallError
    false
  end
end
call(env) click to toggle source
# File lib/rack/tail_file.rb, line 34
def call(env)
  dup._call(env)
end
each() { |part| ... } click to toggle source
# File lib/rack/tail_file.rb, line 147
def each
  F.open(@path, "rb") do |file|
    file.seek(@range.begin)
    remaining_len = @range.end-@range.begin+1
    while remaining_len > 0
      part = file.read([8192, remaining_len].min)
      break unless part
      remaining_len -= part.length

      yield part
    end
  end
end
file_path(env) click to toggle source
# File lib/rack/tail_file.rb, line 85
def file_path(env)
  target_file env
end
method_allowed?(env) click to toggle source
# File lib/rack/tail_file.rb, line 53
def method_allowed? env
  ALLOWED_VERBS.include? env["REQUEST_METHOD"]
end
path_info_for(env) click to toggle source
# File lib/rack/tail_file.rb, line 65
def path_info_for env
  Utils.unescape(env["PATH_INFO"])
end
path_is_within_root?(env) click to toggle source
# File lib/rack/tail_file.rb, line 69
def path_is_within_root? env
  root = root env
  target = target_file env
  !target.relative_path_from(root).to_s.split(SEPS).any?{|p| p == ".."}
end
requested_lines_size(env) click to toggle source
# File lib/rack/tail_file.rb, line 105
def requested_lines_size(env)
  (env.fetch("QUERY_STRING")[/\d+/] || 50).to_i
end
requested_size(env, response) click to toggle source
# File lib/rack/tail_file.rb, line 123
def requested_size(env, response)
  # NOTE:
  #   We check via File::size? whether this file provides size info
  #   via stat (e.g. /proc files often don't), otherwise we have to
  #   figure it out by reading the whole file into memory.
  size = F.size?(@path) || Utils.bytesize(F.read(@path))

  #TODO handle invalid lines
  tail_size = tail_size_for requested_lines_size(env)

  if tail_size == size
    response[0] = 200
    @range = 0..size-1
  else
    start_byte = size - tail_size - 1
    @range = start_byte..size-1
    response[0] = 206
    response[1]["Content-Range"] = "bytes #{@range.begin}-#{@range.end}/#{size}"
    size = @range.end - @range.begin + 1
  end

  size
end
serving(env) click to toggle source
# File lib/rack/tail_file.rb, line 89
def serving(env)
  last_modified = F.mtime(@path).httpdate
  return [304, {}, []] if env['HTTP_IF_MODIFIED_SINCE'] == last_modified

  headers = { "Last-Modified" => last_modified }
  mime = Mime.mime_type(F.extname(@path), @default_mime)
  headers["Content-Type"] = mime if mime

  # Set custom headers
  @headers.each { |field, content| headers[field] = content } if @headers

  response = [ 200, headers, env["REQUEST_METHOD"] == "HEAD" ? [] : self ]
  response[1]["Content-Length"] = requested_size(env, response).to_s
  response
end
tail_size_for(line_count) click to toggle source
# File lib/rack/tail_file.rb, line 109
def tail_size_for line_count
  elif = Elif.new(@path)
  tail_size = 0
  line_count.times do
    begin
      tail_size += Rack::Utils.bytesize(elif.readline)
    rescue EOFError
      return tail_size
    end
  end
  tail_size - 1 # Don't include the first \n
end
target_file(env) click to toggle source
# File lib/rack/tail_file.rb, line 75
def target_file env
  path_info = Pathname.new("").join(*path_info_for(env).split(SEPS))
  root = root env
  root.join(path_info)
end

Private Instance Methods

fail(status, body) click to toggle source
# File lib/rack/tail_file.rb, line 163
def fail(status, body)
  body += "\n"
  [
    status,
    {
      "Content-Type" => "text/plain",
      "Content-Length" => body.size.to_s,
      "X-Cascade" => "pass"
    },
    [body]
  ]
end