2023-05-25 14:04:50 +02:00

172 lines
5.0 KiB
Ruby

# frozen_string_literal: true
require "#{File.dirname(__FILE__)}/uri"
require 'addressable/uri'
module Dav4rack
# Request
class Request < Rack::Request
# Root URI path for the resource
attr_reader :root_uri_path
# options:
#
# recursive_propfind_allowed (true) : set to false to disable
# potentially expensive recursive propfinds
#
def initialize(env, options = {})
super env
@options = { recursive_propfind_allowed: true }.merge options
self.path_info = expand_path path_info
end
def authorization?
!!authorization
end
def authorization
get_header 'HTTP_AUTHORIZATION'
end
# path relative to root uri
def unescaped_path_info
@unescaped_path_info ||= self.class.unescape_path path_info
end
# the full path (script_name aka rack mount point + path_info)
def unescaped_path
@unescaped_path ||= self.class.unescape_path path
end
def self.unescape_path(path)
p = path.dup
p.force_encoding 'UTF-8'
Addressable::URI.unencode p
end
# Namespace being used within XML document
def ns(wanted_uri = XmlElements::DAV_NAMESPACE)
if document && (root = document.root) && (ns_defs = root.namespace_definitions) && !ns_defs.empty?
result = ns_defs.detect { |nd| nd.href == wanted_uri } || ns_defs.first
result = result.prefix.nil? ? 'xmlns' : result.prefix.to_s
result += ':' unless result.empty?
result
else
''
end
end
# Lock token if provided by client
def lock_token
get_header 'HTTP_LOCK_TOKEN'
end
# Requested depth
def depth
@depth ||= if (d = get_header('HTTP_DEPTH')) && %w[0 1].include?(d)
d.to_i
elsif infinity_depth_allowed?
:infinity
else
1
end
end
# Destination header
def destination
unless @destination
h = get_header('HTTP_DESTINATION')
@destination = DestinationHeader.new Dav4rack::Uri.new(h, script_name: script_name) if h
end
@destination
end
# Overwrite is allowed
def overwrite?
get_header('HTTP_OVERWRITE').to_s.casecmp('F').zero?
end
# content_length as a Fixnum (nil if the header is unset / empty)
def content_length
length = super || get_header('HTTP_X_EXPECTED_ENTITY_LENGTH')
length&.to_i
end
# parsed XML request body if any (Nokogiri XML doc)
def document
@request_document ||= parse_request_body if content_length&.positive?
end
# builds a URL for path using this requests scheme, host, port and
# script_name
# path must be valid UTF-8 and will be url encoded by this method
def url_for(rel_path, collection: false)
path = path_for rel_path, collection: collection
"#{scheme}://#{host}:#{port}#{path}"
end
# returns an url encoded, absolute path for the given relative path
def path_for(rel_path, collection: false)
path = Addressable::URI.encode_component rel_path, Addressable::URI::CharacterClasses::PATH
path << '/' if collection && path[-1] != '/'
"#{script_name}#{expand_path path}"
end
# returns the given path, but with the leading script_name removed. Will
# return nil if the path does not begin with the script_name
def path_info_for(full_path, script_name: self.script_name)
uri = Dav4rack::Uri.new full_path, script_name: script_name
uri.path_info
end
# expands '/foo/../bar' to '/bar', peserving trailing slash and normalizing
# consecutive slashes. adds a leading slash if missing
def expand_path(path)
path = path.squeeze '/'
path.prepend '/' unless path[0] == '/'
collection = path.end_with?('/')
path = ::File.expand_path path
path << '/' if collection && !path.end_with?('/')
# remove a drive letter in Windows
path.gsub(%r{^([^/]*)/}, '/')
end
REDIRECTABLE_CLIENTS = [
/cyberduck/i,
/konqueror/i
].freeze
# Does client allow GET redirection
# TODO: Get a comprehensive list in here.
# TODO: Allow this to be dynamic so users can add regexes to match if they know of a client
# that can be supported that is not listed.
def client_allows_redirect?
ua = user_agent
REDIRECTABLE_CLIENTS.any? { |re| ua =~ re }
end
def get_header(name)
@env[name]
end
private
# true if Depth: Infinity is allowed for this request.
#
# http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
# Servers ... should support "infinity" requests. In practice,
# support for infinite-depth requests may be disabled, due to the
# performance and security concerns associated with this behavior
def infinity_depth_allowed?
request_method != 'PROPFIND' or @options[:recursive_propfind_allowed]
end
def parse_request_body
Nokogiri.XML(body.read, &:strict) if body
rescue StandardError => e
Rails.logger.error e.message
raise ::Dav4rack::HttpStatus::BadRequest
end
end
end