|
| 1 | +# frozen-string-literal: true |
| 2 | + |
| 3 | +begin |
| 4 | + require 'rack/files' |
| 5 | +rescue LoadError |
| 6 | + require 'rack/file' |
| 7 | +end |
| 8 | + |
| 9 | +# |
| 10 | +class Roda |
| 11 | + module RodaPlugins |
| 12 | + # The send_file plugin adds a send_file method, used for |
| 13 | + # returning the contents of a file as the body of a request. |
| 14 | + # It also loads the response_attachment plugin to set the |
| 15 | + # Content-Disposition and Content-Type based on the file's |
| 16 | + # extension. |
| 17 | + # |
| 18 | + # senf_file will serve the file with the given path from the file system: |
| 19 | + # |
| 20 | + # send_file 'path/to/file.txt' |
| 21 | + # |
| 22 | + # Options: |
| 23 | + # |
| 24 | + # :disposition :: Set the Content-Disposition to the given disposition. |
| 25 | + # :filename :: Set the Content-Disposition to attachment (unless :disposition is set), |
| 26 | + # and set the filename parameter to the value. |
| 27 | + # :last_modified :: Explicitly set the Last-Modified header to the given value, and |
| 28 | + # return a not modified response if there has not been modified since |
| 29 | + # the previous request. This option requires the caching plugin. |
| 30 | + # :status :: Override the status for the response. |
| 31 | + # :type :: Set the Content-Type to use for this response. |
| 32 | + # |
| 33 | + # == License |
| 34 | + # |
| 35 | + # The implementation was originally taken from Sinatra, |
| 36 | + # which is also released under the MIT License: |
| 37 | + # |
| 38 | + # Copyright (c) 2007, 2008, 2009 Blake Mizerany |
| 39 | + # Copyright (c) 2010, 2011, 2012, 2013, 2014 Konstantin Haase |
| 40 | + # |
| 41 | + # Permission is hereby granted, free of charge, to any person |
| 42 | + # obtaining a copy of this software and associated documentation |
| 43 | + # files (the "Software"), to deal in the Software without |
| 44 | + # restriction, including without limitation the rights to use, |
| 45 | + # copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 46 | + # copies of the Software, and to permit persons to whom the |
| 47 | + # Software is furnished to do so, subject to the following |
| 48 | + # conditions: |
| 49 | + # |
| 50 | + # The above copyright notice and this permission notice shall be |
| 51 | + # included in all copies or substantial portions of the Software. |
| 52 | + # |
| 53 | + # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| 54 | + # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |
| 55 | + # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| 56 | + # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
| 57 | + # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| 58 | + # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| 59 | + # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
| 60 | + # OTHER DEALINGS IN THE SOFTWARE. |
| 61 | + module SendFile |
| 62 | + RACK_FILES = defined?(Rack::Files) ? Rack::Files : Rack::File |
| 63 | + |
| 64 | + # Depend on the status_303 plugin. |
| 65 | + def self.load_dependencies(app) |
| 66 | + app.plugin :response_attachment |
| 67 | + end |
| 68 | + |
| 69 | + module InstanceMethods |
| 70 | + # Use the contents of the file at +path+ as the response body. See plugin documentation for options. |
| 71 | + def send_file(path, opts = OPTS) |
| 72 | + r = @_request |
| 73 | + res = @_response |
| 74 | + headers = res.headers |
| 75 | + if (type = opts[:type]) || !headers[RodaResponseHeaders::CONTENT_TYPE] |
| 76 | + type_str = type.to_s |
| 77 | + |
| 78 | + if type_str.include?('/') |
| 79 | + type = type_str |
| 80 | + else |
| 81 | + if type |
| 82 | + type = ".#{type}" unless type_str.start_with?(".") |
| 83 | + else |
| 84 | + type = ::File.extname(path) |
| 85 | + end |
| 86 | + |
| 87 | + type &&= Rack::Mime.mime_type(type, nil) |
| 88 | + type ||= 'application/octet-stream' |
| 89 | + end |
| 90 | + |
| 91 | + headers[RodaResponseHeaders::CONTENT_TYPE] = type |
| 92 | + end |
| 93 | + |
| 94 | + disposition = opts[:disposition] |
| 95 | + filename = opts[:filename] |
| 96 | + if disposition || filename |
| 97 | + disposition ||= 'attachment' |
| 98 | + filename = path if filename.nil? |
| 99 | + res.attachment(filename, disposition) |
| 100 | + end |
| 101 | + |
| 102 | + if lm = opts[:last_modified] |
| 103 | + r.last_modified(lm) |
| 104 | + end |
| 105 | + |
| 106 | + file = RACK_FILES.new nil |
| 107 | + s, h, b = if Rack.release > '2' |
| 108 | + file.serving(r, path) |
| 109 | + else |
| 110 | + file.path = path |
| 111 | + file.serving(env) |
| 112 | + end |
| 113 | + |
| 114 | + res.status = opts[:status] || s |
| 115 | + headers.delete(RodaResponseHeaders::CONTENT_LENGTH) |
| 116 | + headers.replace(h.merge!(headers)) |
| 117 | + r.halt res.finish_with_body(b) |
| 118 | + rescue Errno::ENOENT |
| 119 | + response.status = 404 |
| 120 | + r.halt |
| 121 | + end |
| 122 | + end |
| 123 | + end |
| 124 | + |
| 125 | + register_plugin(:send_file, SendFile) |
| 126 | + end |
| 127 | +end |
0 commit comments