lib/roda/plugins/send_file.rb
# frozen-string-literal: true begin require 'rack/files' rescue LoadError require 'rack/file' end # class Roda module RodaPlugins # The send_file plugin adds a send_file method, used for # returning the contents of a file as the body of a request. # It also loads the response_attachment plugin to set the # Content-Disposition and Content-Type based on the file's # extension. # # senf_file will serve the file with the given path from the file system: # # send_file 'path/to/file.txt' # # Options: # # :disposition :: Set the Content-Disposition to the given disposition. # :filename :: Set the Content-Disposition to attachment (unless :disposition is set), # and set the filename parameter to the value. # :last_modified :: Explicitly set the Last-Modified header to the given value, and # return a not modified response if there has not been modified since # the previous request. This option requires the caching plugin. # :status :: Override the status for the response. # :type :: Set the Content-Type to use for this response. # # == License # # The implementation was originally taken from Sinatra, # which is also released under the MIT License: # # Copyright (c) 2007, 2008, 2009 Blake Mizerany # Copyright (c) 2010, 2011, 2012, 2013, 2014 Konstantin Haase # # 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. module SendFile RACK_FILES = defined?(Rack::Files) ? Rack::Files : Rack::File # Depend on the status_303 plugin. def self.load_dependencies(app) app.plugin :response_attachment end module InstanceMethods # Use the contents of the file at +path+ as the response body. See plugin documentation for options. def send_file(path, opts = OPTS) r = @_request res = @_response headers = res.headers if (type = opts[:type]) || !headers[RodaResponseHeaders::CONTENT_TYPE] type_str = type.to_s if type_str.include?('/') type = type_str else if type type = ".#{type}" unless type_str.start_with?(".") else type = ::File.extname(path) end type &&= Rack::Mime.mime_type(type, nil) type ||= 'application/octet-stream' end headers[RodaResponseHeaders::CONTENT_TYPE] = type end disposition = opts[:disposition] filename = opts[:filename] if disposition || filename disposition ||= 'attachment' filename = path if filename.nil? res.attachment(filename, disposition) end if lm = opts[:last_modified] r.last_modified(lm) end file = RACK_FILES.new nil s, h, b = if Rack.release > '2' file.serving(r, path) else file.path = path file.serving(env) end res.status = opts[:status] || s headers.delete(RodaResponseHeaders::CONTENT_LENGTH) headers.replace(h.merge!(headers)) r.halt res.finish_with_body(b) rescue Errno::ENOENT response.status = 404 r.halt end end end register_plugin(:send_file, SendFile) end end