require 'addressable/uri' module Scrivito class LinkParser EDITING_CONTEXT_PARAMS = [ '_scrivito_display_mode'.freeze, '_scrivito_workspace_id'.freeze, ].freeze EDITING_CONTEXT_AND_ENCRYPTED_PARAMS = ( EDITING_CONTEXT_PARAMS + [ 'encrypted_params'.freeze ] ).freeze ENCRYPTED_PARAMS = 'encrypted_params'.freeze SCRIVITO_BINARY_REDIRECT = 'scrivito/binary_redirect'.freeze SCRIVITO_CMS_DISPATCH = 'scrivito/cms_dispatch'.freeze SCRIVITO_UI_PATH = 'scrivito/ui'.freeze def initialize(host, port) @host = host @port = port end def parse(url) uri = Addressable::URI.parse(url) return Link.new(url: url) unless application_uri?(uri) uri.path = remove_ui_path(uri) route_params = recognize_path(uri.to_s) if obj = find_obj_via_id_or_permalink(route_params) Link.new( obj: obj, query: remove_params(uri.query_values, EDITING_CONTEXT_PARAMS), fragment: uri.fragment, ) elsif obj = find_obj_via_binary(route_params, uri) Link.new( obj: obj, query: remove_params(uri.query_values, EDITING_CONTEXT_AND_ENCRYPTED_PARAMS), fragment: uri.fragment, ) else uri.path = '/' if uri.path.blank? uri.port = nil uri.host = nil uri.scheme = nil Link.new(url: uri.to_s) end rescue Addressable::URI::InvalidURIError Link.new(url: url) end private def remove_ui_path(uri) if (params = recognize_precedence_path(uri.to_s)) && params[:controller] == SCRIVITO_UI_PATH params[:application_path] else uri.path end end def application_uri?(uri) absolute_uri?(uri) && internal_uri?(uri) || relative_uri?(uri) && uri.path.present? end def absolute_uri?(uri) # Ruby's URI parser assumes URLs without a scheme as relative, while a browser assumes a # schemeless "//scrivito.com" as absolute. Since the URLs are for the browser, we cannot use # Ruby's URI here. # See issue https://github.com/sporkmonger/addressable/issues/147 for details. uri.to_s =~ /\A([a-z][a-z\d\-+\.]*:|\/\/).*/i end def relative_uri?(uri) !absolute_uri?(uri) end def internal_uri?(uri) if uri.port.present? uri.host == @host && uri.port == Integer(@port) else uri.host == @host end end def find_obj_via_id_or_permalink(route_params) return unless route_params[:controller] == SCRIVITO_CMS_DISPATCH if id = route_params[:id] Obj.find(id) elsif permalink = route_params[:permalink] Obj.find_by_permalink(permalink) end rescue Scrivito::ResourceNotFound end def find_obj_via_binary(route_params, uri) return unless route_params[:controller] == SCRIVITO_BINARY_REDIRECT encrypted_params = uri.query_values[ENCRYPTED_PARAMS] binary = BinaryParamVerifier.verify_without_expire(encrypted_params) Obj.find(binary.obj_id) rescue Scrivito::ResourceNotFound, Scrivito::BinaryParamVerifier::InvalidSignature end def recognize_path(url) recognize_precedence_path(url) || recognize_app_path(url) || {} end def recognize_precedence_path(url) Scrivito::SdkEngine.routes.recognize_path(url, method: :get) rescue ActionController::RoutingError end def recognize_app_path(url) Rails.application.routes.recognize_path(url, method: :get) rescue ActionController::RoutingError end def remove_params(query_values, params_to_remove) if query_values filtered_query_values = query_values.except!(*params_to_remove) uri = Addressable::URI.new(query_values: filtered_query_values) uri.query.presence end end end end