# Relative Path plugin # ## usage 1 : # class YourController < ApplicationController # include RelativePath # end ## usage 2 : # class ApplicationController # include RelativePath # end ## An example to use Static File Filter is below: # RelativePath.register_filter /\.css/,%r(src="(\.\./themes.+?)") do |env,match| # referer = env["HTTP_REFERER"] # if (req_uri = env["CLIENT_REQUEST_URI"]) && referer then # referer = URI(referer) # rel_path = req_uri + match[1] - referer # "src=\"#{rel_path}\"" # else # match[0] # end # end ## first argument /\.css/ is a regular expression to specify files ## you want to proccess. ## This filter replace strings matched by the second argument ## with block's evaluated value. ## block's first argument *env* is a hash. The content of env is ## almost equal to HTTP Request header fields ## except that it is added CLIENT_REQUEST_URI. ## env["CLIENT_REQUEST_URI"] contains an induced uri that is ## on user's browser. Keep it in mind that the inducing method is ## imperfect. CLIENT_REQUEST_URI is useful when your CSS needs ## relative path from a user's browsing uri. ## block's second argument *match* is MatchData object that ## is matched by the regular expression of the second argument ## of register_filter method. ## Because the matched string is replaced by the block's ## evaluated value, you have to return match[0] when you don't want ## to change anything. # ## An example to use Togglable Relative Path feature below: # <% with_relative_path_disabled do %> # <%= link_to 'Show', :action => 'show', :id => user_id, :only_path => false %> # <% with_relative_path_enabled do %> # <%= link_to 'Edit', :action => 'edit', :id = user_id %> # <% end %> # <%= link_to 'Destroy', { :action => 'destroy', :id => user_id }, :confirm => 'Are you sure?', :method => :post %> # <% end %> # module RelativePath def self.append_features(klass) @klass = klass super klass klass.extend(ClassMethods) klass.init_relative_path if RAILS_GEM_VERSION < "2" klass.__send__ :include, RelativePath_for_Rails1 else klass.send! :include, RelativePath_for_Rails2 end end def relative_path_prefix parent_dir = "." path = "" if request.xhr? referer = URI.parse(request.env["HTTP_REFERER"]).path if referer =~ %r((/#{self.class.controller_path}.+)) path = $1 end else path = URI.parse(request.env["REQUEST_URI"]).path root = request.relative_url_root.to_s path.gsub!(root,"") end count = path.count("/") - 1 if count > 0 parents = [".."] * count parent_dir = parents.join("/") end parent_dir end def relative_path url if url =~ %r(^/) then parent_dir = relative_path_prefix root = request.relative_url_root.to_s url.gsub!(root,"") url = "#{parent_dir}#{url}" end url end def redirect_to(options = {}, *status) unless self.class.relative_path_enabled? return super(options, *status) end case options when %r(^\.) raise DoubleRenderError if performed? logger.info("Redirected to #{options}") if logger && logger.info? response.redirect(options, interpret_status(*status)) response.redirected_to = options @performed_redirect = true when %r(^/) url = relative_path(options) redirect_to url, *status when String super when Hash options[:only_path] = true options[:skip_relative_url_root] = true super(options, *status) else super(options, *status) end end module RelativePath_for_Rails1 def url_for(options = {}, *parameters_for_method_reference) unless self.class.relative_path_enabled? return super(options, *parameters_for_method_reference) end options = {} if options.nil? case options when Hash options[:only_path] = true options[:skip_relative_url_root] = true url = super(options, *parameters_for_method_reference) return relative_path(url) else return super(options, *parameters_for_method_reference) end end def initialize_current_url super self.class.class_eval do view_class.class_eval do break if @already_redefined_for_relative_path @already_redefined_for_relative_path = true define_method(:compute_public_path) do |source,dir,ext| url = super source,dir,ext break url unless @controller.class.relative_path_enabled? @controller.relative_path(url) end define_method(:url_for) do |options,*parameters_for_method_reference| url = super options,*parameters_for_method_reference break url unless @controller.class.relative_path_enabled? @controller.relative_path(url) end define_method(:current_page?) do |options| if @controller.class.relative_path_enabled? req_uri = @controller.request.request_uri rel_uri = CGI.escapeHTML(url_for(options)) cur_uri = URI.join("http://dummy/", req_uri, rel_uri).path req_uri == cur_uri or req_uri == cur_uri + "/" else super options end end end end set_relative_url_root end def set_relative_url_root if defined?(@@relative_url_root_callback) @@relative_url_root = @@relative_url_root_callback.call(self) elsif not defined?(@@relative_url_root) or @@relative_url_root.nil? or @@relative_url_root.empty? @@relative_url_root = default_relative_url_root end end end module RelativePath_for_Rails2 def self.append_features klass super klass kontroller = klass ::ActionController::Routing::Routes.named_routes.instance_eval do named_route = self absolute_helpers = [] helpers.each do |selector| absolute_selector = :"absolute_#{selector}" @module.module_eval do break if method_defined? absolute_selector alias_method absolute_selector, selector define_method selector do |*options| url = __send__ absolute_selector, *options if self.is_a? kontroller relative_path(url) elsif(defined?(controller) and controller.is_a?(kontroller)) controller.relative_path(url) else url end end end absolute_helpers << absolute_selector end helpers.concat absolute_helpers end end def url_for(options = {}) unless self.class.relative_path_enabled? return super(options) end options = {} if options.nil? case options when Hash options[:only_path] = true options[:skip_relative_url_root] = true url = super(options) return relative_path(url) else return super(options) end end def initialize_current_url super controller = self klass = self.class response.template.instance_eval do break if @already_redefined_for_relative_path @already_redefined_for_relative_path = true def url_for options url = super options flag = false return url unless controller.class.relative_path_enabled? controller.relative_path(url) end def compute_public_path *args url = super *args return url unless controller.class.relative_path_enabled? controller.relative_path(url) end def current_page? options if controller.class.relative_path_enabled? req_uri = controller.request.request_uri rel_uri = CGI.escapeHTML(url_for(options)) cur_uri = URI.join("http://dummy/", req_uri, rel_uri).path req_uri == cur_uri or req_uri == cur_uri + "/" else super options end end end end end # this method is imperfect. def default_relative_url_root if referer = request.env["HTTP_REFERER"] path = URI(referer).path controller_path = self.class.controller_path if index = path.index(controller_path) then return path[0,index] end end end def self.disable_static_file_filter @static_file_filter_disabled = true end def self.static_file_filter_disabled? @static_file_filter_disabled end module StaticFileFilter @@filter_callbacks = nil def self.calc_client_request_uri h if referer = h["HTTP_REFERER"] then rel_root = RelativePath.relative_url_root.to_s.dup if rel_root.length > 0 and rel_root[-1].chr != "/" rel_root << "/" end referer = URI(referer) client_request_uri = case str = h["REQUEST_URI"] when URI uri = str referer + rel_root + ".#{uri.path}" when %r(^http://) uri = URI(str) referer + rel_root + ".#{uri.path}" when %r(^/) referer + rel_root + ".#{str}" else str end h["CLIENT_REQUEST_URI"] = client_request_uri end end def self.webrick_filter h return unless @@filter_callbacks calc_client_request_uri h @@filter_callbacks.each do |filename_regex,src_regex,callback| next unless h[:local_path] =~ filename_regex h[:io].instance_eval do @filter_callbacks ||= [] @filter_callbacks << [src_regex,callback] @filter_env = h def read size body = super size @filter_callbacks.each do |src_regex,callback| body.gsub!(src_regex) do |*args| callback.call @filter_env,Regexp.last_match,*args end end return body end end end end def self.mongrel_filter h body = h[:io].string return body unless @@filter_callbacks calc_client_request_uri h @@filter_callbacks.each do |filename_regex,pattern,replace| next unless h[:local_path] =~ filename_regex body.gsub!(pattern) do |*args| replace.call h,Regexp.last_match end end return body end def self.register_filter filename_regex,pattern,&replace @@filter_callbacks ||= [] @@filter_callbacks << [filename_regex,pattern,replace] end end # # this method is also imperfect def self.induce_relative_url_root referer return unless referer path = URI(referer).path cpath = ActionController::Base.controller_path index = path.index(cpath) if index && index > 0 then @@relative_url_root = path[0,index] else @@relative_url_root = "./" end end module ClassMethods def init_relative_path enable_relative_path if defined?(WEBrick) then init_webrick end if defined?(Mongrel) then init_mongrel end init_abstract_request end def enable_relative_path @relative_path_enabled = true end def disable_relative_path @relative_path_enabled = false end def with_relative_path_enabled tmp = @relative_path_enabled enable_relative_path yield @relative_path_enabled = tmp end def with_relative_path_disabled tmp = @relative_path_enabled disable_relative_path yield @relative_path_enabled = tmp end def relative_path_enabled? case @relative_path_enabled when true return true when false return false end klass = self.superclass while klass < ActionController::Base case klass.instance_variable_get :@relative_path_enabled when true @relative_path_enabled = true return true when false @relative_path_enabled = false return false end klass = klass.superclass end @relative_path_enabled = true return true end def relative_url_root if defined?(@@relative_url_root) @@relative_url_root else "" end end def set_relative_url_root rel_root = nil,&callback if callback then @@relative_url_root_callback = callback elsif rel_root then @@relative_url_root = rel_root end end def register_filter *args,&callback StaticFileFilter.register_filter *args,&callback end def init_webrick klass = self WEBrick::HTTPResponse.class_eval do break if defined?(@setup_header_redefined) @setup_header_redefined = true unbound = instance_method :setup_header define_method :setup_header do if klass.relative_path_enabled? location = @header['location'] unbound.bind(self).call if location =~ %r(^\.) then @header['location'] = location end else unbound.bind(self).call end end end return if RelativePath.static_file_filter_disabled? HTTPServlet::DefaultFileHandler.class_eval do break if defined?(@do_GET_redefined) @do_GET_redefined = true unbound = instance_method :do_GET define_method :do_GET do |req,res| ret = unbound.bind(self).call(req,res) break ret if RelativePath.static_file_filter_disabled? if res.body.is_a?(File) begin h = req.meta_vars.dup RelativePath.induce_relative_url_root h["HTTP_REFERER"] h.merge!({:local_path => @local_path, :io => res.body, }) RelativePath::StaticFileFilter.webrick_filter h rescue => e $stderr.puts e.to_s $stderr.puts $@.first(5) end end break ret end end end def init_mongrel return if RelativePath.static_file_filter_disabled? Mongrel::DirHandler.class_eval do break if defined?(@send_file_redefined) @send_file_redefined = true unbound = instance_method :send_file define_method :send_file do |req_path,req,response,header_only| header_only ||= false socket_escaped = nil begin response.instance_eval do socket_escaped = @socket @socket = StringIO.new("","w") end begin method = unbound.bind self ret = method.call req_path,req,response,header_only rescue => e $stderr.puts e.to_s $stderr.puts $@.first(5) end return ret ensure io = nil response.instance_eval do io = @socket @socket = socket_escaped end h = req.params.dup h.merge!({:local_path => req_path, :io => io, }) RelativePath.induce_relative_url_root(h["HTTP_REFERER"]) begin string = RelativePath::StaticFileFilter.mongrel_filter h response.instance_eval do write(string) end rescue => e $stderr.puts e.to_s $stderr.puts $@.first(5) end end end end end def init_abstract_request ActionController::AbstractRequest.class_eval do break if defined?(@relative_url_root_redefined) @relative_url_root_redefined = true define_method :relative_url_root do @@relative_url_root ||= case when @env["RAILS_RELATIVE_URL_ROOT"] @env["RAILS_RELATIVE_URL_ROOT"] when ENV["RAILS_RELATIVE_URL_ROOT"] ENV["RAILS_RELATIVE_URL_ROOT"] when server_software == 'apache' File.dirname(@env["SCRIPT_NAME"].to_s) when server_software.to_s.include?("lighttpd") File.dirname(@env["SCRIPT_NAME"].to_s).sub(%r(/$),"") else nil end @@relative_url_root = nil if @@relative_url_root == "" end end end end def enable_relative_path self.class.enable_relative_path end def disable_relative_path self.class.disable_relative_path end def with_relative_path_enabled self.class.with_relative_path_enabled do yield end end def with_relative_path_disabled self.class.with_relative_path_disabled do yield end end end module RelativePathHelper def with_relative_path_enabled controller.with_relative_path_enabled do yield end end def with_relative_path_disabled controller.with_relative_path_disabled do yield end end end module ApplicationHelper include RelativePathHelper end