150 lines
4.7 KiB
Ruby
150 lines
4.7 KiB
Ruby
require 'net/http'
|
|
require 'uri'
|
|
require 'digest/sha1'
|
|
require 'rack/file'
|
|
|
|
module Dav4rack
|
|
|
|
#FIXME unused?
|
|
class RemoteFile < Rack::File
|
|
|
|
attr_accessor :path
|
|
|
|
alias :to_path :path
|
|
|
|
# path:: path to file (Actual path, preferably a URL since this is a *REMOTE* file)
|
|
# args:: Hash of arguments:
|
|
# :size -> Integer - number of bytes
|
|
# :mime_type -> String - mime type
|
|
# :last_modified -> String/Time - Time of last modification
|
|
# :sendfile -> True or String to define sendfile header variation
|
|
# :cache_directory -> Where to store cached files
|
|
# :cache_ref -> Reference to be used for cache file name (useful for changing URLs like S3)
|
|
# :sendfile_prefix -> String directory prefix. Eg: 'webdav' will result in: /wedav/#{path.sub('http://', '')}
|
|
# :sendfile_fail_gracefully -> Boolean if true will simply proxy if unable to determine proper sendfile
|
|
def initialize(path, args={})
|
|
@path = path
|
|
@args = args
|
|
@heads = {}
|
|
@cache_file = args[:cache_directory] ? cache_file_path : nil
|
|
@redefine_prefix = nil
|
|
if(@cache_file && File.exist?(@cache_file))
|
|
@root = ''
|
|
@path_info = @cache_file
|
|
@path = @path_info
|
|
elsif(args[:sendfile])
|
|
@redefine_prefix = 'sendfile'
|
|
@sendfile_header = args[:sendfile].is_a?(String) ? args[:sendfile] : nil
|
|
else
|
|
setup_remote
|
|
end
|
|
do_redefines(@redefine_prefix) if @redefine_prefix
|
|
end
|
|
|
|
# env:: Environment variable hash
|
|
# Process the call
|
|
def call(env)
|
|
serving(env)
|
|
end
|
|
|
|
# env:: Environment variable hash
|
|
# Return an empty result with the proper header information
|
|
def sendfile_serving(env)
|
|
header = @sendfile_header || env['sendfile.type'] || env['HTTP_X_SENDFILE_TYPE']
|
|
unless(header)
|
|
raise 'Failed to determine proper sendfile header value' unless @args[:sendfile_fail_gracefully]
|
|
setup_remote
|
|
do_redefines('remote')
|
|
call(env)
|
|
end
|
|
prefix = (@args[:sendfile_prefix] || env['HTTP_X_ACCEL_REMOTE_MAPPING']).to_s.sub(/^\//, '').sub(/\/$/, '')
|
|
[200, {
|
|
"Last-Modified" => last_modified,
|
|
"Content-Type" => content_type,
|
|
"Content-Length" => size,
|
|
"Redirect-URL" => @path,
|
|
"Redirect-Host" => @path.scan(%r{^https?://([^/\?]+)}).first.first,
|
|
header => "/#{prefix}"
|
|
},
|
|
['']]
|
|
end
|
|
|
|
# env:: Environment variable hash
|
|
# Return self to be processed
|
|
def remote_serving(e)
|
|
[200, {
|
|
"Last-Modified" => last_modified,
|
|
"Content-Type" => content_type,
|
|
"Content-Length" => size
|
|
}, self]
|
|
end
|
|
|
|
# Get the remote file
|
|
def remote_each
|
|
if(@store)
|
|
yield @store
|
|
else
|
|
@con.request_get(@call_path) do |res|
|
|
res.read_body(@store) do |part|
|
|
@cf.write part if @cf
|
|
yield part
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
# Size based on remote headers or given size
|
|
def size
|
|
@heads['content-length'] || @size.to_s
|
|
end
|
|
|
|
private
|
|
|
|
# Content type based on provided or remote headers
|
|
def content_type
|
|
@mime_type || @heads['content-type']
|
|
end
|
|
|
|
# Last modified type based on provided, remote headers or current time
|
|
def last_modified
|
|
@heads['last-modified'] || @modified || Time.now.httpdate
|
|
end
|
|
|
|
# Builds the path for the cached file
|
|
def cache_file_path
|
|
raise IOError.new 'Write permission is required for cache directory' unless File.writable?(@args[:cache_directory])
|
|
"#{@args[:cache_directory]}/#{Digest::SHA1.hexdigest((@args[:cache_ref] || @path).to_s + size.to_s + last_modified.to_s)}.cache"
|
|
end
|
|
|
|
# prefix:: prefix of methods to be redefined
|
|
# Redefine methods to do what we want in the proper situation
|
|
def do_redefines(prefix)
|
|
self.public_methods.each do |method|
|
|
m = method.to_s.dup
|
|
next unless m.slice!(0, prefix.to_s.length + 1) == "#{prefix}_"
|
|
self.class.class_eval "undef :'#{m}'"
|
|
self.class.class_eval "alias :'#{m}' :'#{method}'"
|
|
end
|
|
end
|
|
|
|
# Sets up all the requirements for proxying a remote file
|
|
def setup_remote
|
|
if(@cache_file)
|
|
begin
|
|
@cf = File.open(@cache_file, 'w+')
|
|
rescue
|
|
@cf = nil
|
|
end
|
|
end
|
|
@uri = URI.parse(@path)
|
|
@con = Net::HTTP.new(@uri.host, @uri.port)
|
|
@call_path = @uri.path + (@uri.query ? "?#{@uri.query}" : '')
|
|
res = @con.request_get(@call_path)
|
|
@heads = res.to_hash
|
|
res.value
|
|
@store = nil
|
|
@redefine_prefix = 'remote'
|
|
end
|
|
end
|
|
end
|