rp_filter (private)
rp_filter why
Defined in packages/acs-tcl/tcl/request-processor-procs.tcl
This is the first filter that runs for non-resource URLs. It sets up ad_conn and handles session security.
- Parameters:
- why (required)
- Partial Call Graph (max 5 caller/called nodes):
- Testcases:
- No testcase defined.
Source code: ##### # # Initialize the environment: reset ad_conn, and populate it with # a few things. # ##### fix_cookies sec_handler_reset ad_conn -reset ad_conn -set request [ns_conn id] ad_conn -set user_id 0 ad_conn -set start_clicks [clock clicks -microseconds] ds_collect_connection_info # ------------------------------------------------------------------------- # Start of patch "hostname-based subsites" # ------------------------------------------------------------------------- # 1. determine the root of the host and the requested URL ad_try { set root [acs::root_of_host [ad_host]] } on error {errorMsg} { ad_log warning "rp_filter: acs::root_of_host returned error: $errorMsg" ad_page_contract_handle_datasource_error "Host header is invalid" return filter_return } set ad_conn_url [ad_conn url] ad_conn -set vhost_url $ad_conn_url # # Check for invalid characters om the URL. # if {[regexp {[^[:print:]]} $ad_conn_url]} { ad_log warning "rp_filter: BAD CHAR in URL $ad_conn_url // rp_filter $why" # # Reset [ad_conn url], otherwise we might run into a problem # when rendering the error page. # ad_conn -set url ${root}/ ad_page_contract_handle_datasource_error "URL contains invalid characters" return filter_return } # # To test whether the URL chars are accepted by PostgreSQL, one # might activate the following line. # # xo::dc get_value x "select 1 from cr_items where name = :ad_conn_url" # if {[string length $ad_conn_url] > [parameter::get -package_id $::acs::kernel_id -parameter MaxUrlLength -default 2000]} { ad_log warning "rp_filter: URL TOO LONG: <$ad_conn_url> rp_filter $why" # # Reset [ad_conn url], otherwise we might run into a problem # when rendering the error page. # ad_conn -set url ${root}/ ad_page_contract_handle_datasource_error "URL is longer than allowed" return filter_return } # # UseCanonicalLocation is a experimental feature, not to be # activated for the OpenACS 5.9.1 release. One can use this to # force requests submitted to an alternate DNS entry to be # redirected to a canonical name. For more background, see: # https://support.google.com/webmasters/answer/139066?hl=en # https://webmasters.stackexchange.com/questions/44830/should-i-redirect-the-site-ip-address-to-the-domain-name # #ns_log notice "CHECK ad_conn_url <$ad_conn_url>" if {[parameter::get -package_id [ad_acs_kernel_id] -parameter UseCanonicalLocation -default 1] && ![string match "/.well-known/acme-challenge/*" $ad_conn_url] && ![string match "/SYSTEM*" $ad_conn_url]} { set canonical_location [parameter::get -package_id [ad_acs_kernel_id] -parameter SystemURL] set current_location [util_current_location] # # It might be useful in the future to define per-subsite # CanonicalLocations, and/or combine this with the host-node-map # if {[string index $canonical_location end] eq "/"} { set canonical_location [string trimright $canonical_location /] } if {[ns_conn driver] ne "nsssl"} { ns_log notice "[ns_conn driver] [ns_conn peeraddr] location [ns_conn location] === URL $ad_conn_url current <$current_location> canonical <$canonical_location>" } if {$current_location ne $canonical_location && $current_location ne "https://openacs.org:8443"} { set q [ns_conn query] if {$q ne ""} {append ad_conn_url ?$q} ns_log notice "map location $current_location to canonical ns_returnmoved $canonical_location$ad_conn_url" ns_returnmoved $canonical_location$ad_conn_url return filter_return } } # # Check, if we are supposed to upgrade insecure requests. This # should be after the canonical check to avoid multiple redirects. # The W3C spec (https://www.w3.org/TR/upgrade-insecure-requests/) # requires explicitly the value of "1". By testing this, we # mitigate attacks against this header field without losing # performance. # set upgrade_insecure_requests_p [ns_set iget [ns_conn headers] Upgrade-Insecure-Requests] if {$upgrade_insecure_requests_p ne "" && $upgrade_insecure_requests_p eq "1" && [security::https_available_p] && ![security::secure_conn_p] } { security::redirect_to_secure -script_abort=false [ad_return_url -qualified] return filter_return } # 2. handle special case: if the root is a prefix of the URL, # remove this prefix from the URL, and redirect. if { $root ne "" } { if { [regexp "^${root}(.*)$" $ad_conn_url match url] } { if { [regexp {^GET [^\?]*\?(.*) HTTP} [ns_conn request] match vars] } { append url ?$vars } if { [security::secure_conn_p] } { # it's a secure connection. ns_returnmoved https://[ad_host][ad_port]$url return filter_return } else { ns_returnmoved http://[ad_host][ad_port]$url return filter_return } } # Normal case: Prepend the root to the URL. # 3. set the intended URL ad_conn -set url ${root}${ad_conn_url} ad_conn -set vhost_url ${ad_conn_url} set ad_conn_url [ad_conn url] # 4. set urlv and urlc for consistency set urlv [lrange [split $root /] 1 end] ad_conn -set urlc [expr {[ad_conn urlc] + [llength $urlv]}] ad_conn -set urlv [concat $urlv [ad_conn urlv]] } # ------------------------------------------------------------------------- # End of patch "hostname-based subsites" # ------------------------------------------------------------------------- # Force the URL to look like [ns_conn location], if desired... # JCD: Only do this if ForceHostP set and root is {} # if root non empty then we had a hostname based subsite and # should not redirect since we got a hostname we know about. if { $root eq "" && [parameter::get -package_id $::acs::kernel_id -parameter ForceHostP -default 0] } { set host_header [ns_set iget [ns_conn headers] "Host"] regexp {^([^:]*)} $host_header "" host_no_port regexp {^https?://([^:]+)} [ns_conn location] "" desired_host_no_port if { $host_header ne "" && $host_no_port ne $desired_host_no_port } { set query [ns_getform] if { $query ne "" } { set query ?[export_vars -entire_form] } ad_returnredirect -allow_complete_url "[ns_conn location][ns_conn url]$query" return filter_return } } # DRB: a bug in ns_conn causes urlc to be set to one greater than the number of URL # directory elements and the trailing element of urlv to be set to # {} if you hit the site with the hostname alone. This confuses code that # expects urlc to be set to the length of urlv and urlv to have a non-null # trailing element except in the case where urlc is 0 and urlv the empty list. if { [lindex [ad_conn urlv] end] eq "" } { ad_conn -set urlc [expr {[ad_conn urlc] - 1}] ad_conn -set urlv [lrange [ad_conn urlv] 0 end-1] } rp_debug -ns_log_level debug -debug t "rp_filter: setting up request: [ns_conn method] [ns_conn url] [ns_conn query]" ad_try { set node [site_node::get -url $ad_conn_url] } on error {errorMsg} { # log and do nothing ad_log error "rp_filter: site_node::get for url $ad_conn_url returns: $errorMsg" } on ok {r} { # # When the package is mounted, but not enabled, treat it like # a subsite node. Otherwise, we see unfriendly error messages # about non-instantiated nsvs when e.g. automated testing is # disabled. # if {![apm_package_enabled_p [dict get $node package_key]]} { set node [site_node::get -url /] } if {[dict get $node url] eq "$ad_conn_url/"} { ad_returnredirect [ad_conn vhost_url]/ rp_debug "rp_filter: returnredirect [ad_conn vhost_url]/" rp_debug "rp_filter: return filter_return" return filter_return } ad_conn -set node_id [dict get $node node_id] ad_conn -set node_name [dict get $node name] ad_conn -set object_id [dict get $node object_id] ad_conn -set object_url [dict get $node url] ad_conn -set object_type [dict get $node object_type] ad_conn -set package_id [dict get $node object_id] ad_conn -set package_key [dict get $node package_key] ad_conn -set package_url [dict get $node url] ad_conn -set instance_name [dict get $node instance_name] ad_conn -set extra_url [string trimleft [string range $ad_conn_url [string length [dict get $node url]] end] /] rp_debug "rp_filter: sets extra_url '[ad_conn extra_url]'" } ##### # # See if any libraries have changed. This may look expensive, but all it # does is check an NSV. # ##### if { ![rp_performance_mode] } { # # We wrap this call in a "try", because we don't want an error # exception to cause the full request to fail. # ad_try { apm_load_any_changed_libraries } on error {errorMsg} { ns_log Error "rp_filter: error apm_load_any_changed_libraries: $::errorInfo" } } ##### # # Read in and/or generate security cookies. # ##### # sec_handler (defined in security-procs.tcl) sets the ad_conn # session-level variables such as user_id, session_id, etc. we can # call sec_handler at this point because the previous return # statements are all error-throwing cases or redirects. # ns_log Notice "rp_filter: OACS= RP start" sec_handler # ns_log Notice "rp_filter: OACS= RP end" # Set locale and language of the request. # We need ad_conn user_id to be set at this point ad_try { set locale [lang::conn::locale -package_id [ad_conn package_id]] ad_conn -set locale $locale ad_conn -set language [lang::conn::language -locale $locale] ad_conn -set charset [lang::util::charset_for_locale $locale] } on error {errorMsg} { ns_log warning "rp_filter: language setup failed: $errorMsg" ad_return_complaint 1 "invalid language settings" rp_finish_serving_page return filter_return } set headers [ns_conn headers] if {[ns_info name] eq "NaviServer"} { # # Provide context information for background writer. # set requester [expr {$::ad_conn(user_id) == 0 ? [ad_conn peeraddr] : $::ad_conn(user_id)}] # # Leave for the time being the catch, since a fail of the # primitive function has no user-level consequences, and no # abort operations can happen in the called functions. # catch {ns_conn clientdata [list $requester [ns_conn url]]} } # Who's online whos_online::user_requested_page [ad_conn untrusted_user_id] # # The actual (untrused) user_id can be added to the access.log by # configuring: # # ns_section ns/server/$server/acs # ns_param LogIncludeUserId 1 # if {[ns_config "ns/server/[ns_info server]/acs" LogIncludeUserId 0]} { ns_set put [ns_conn headers] X-User-Id [ad_conn untrusted_user_id] } ##### # # Make sure the user is authorized to make this request. # ##### set result filter_ok if { [ad_conn object_id] ne "" } { ad_try -auto_abort=false { switch -nocase -glob -- [ad_conn extra_url] { admin/* { # # Double check if someone has not accidentally # granted admin to the public; furthermore, require # login for all admin pages. # auth::require_login permission::require_permission -object_id [ad_conn object_id] -privilege admin } sitewide-admin/* { permission::require_permission -object_id [acs_magic_object security_context_root] -privilege admin } default { # ns_log notice "rp_filter calls: permission::require_permission -object_id [ad_conn object_id] -privilege read" permission::require_permission -object_id [ad_conn object_id] -privilege read } } } trap {AD EXCEPTION ad_script_abort} {r} { rp_finish_serving_page rp_debug "rp_filter: page aborted return filter_return" ns_log notice "rp_filter: aborted url <[ad_conn extra_url]> '$r'" set result filter_return } on ok {r} { rp_debug "rp_filter: return filter_ok" } } return $resultXQL Not present: Generic, PostgreSQL, Oracle