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):
%3 rp_handler rp_handler (private) rp_filter rp_filter rp_handler->rp_filter acs::root_of_host acs::root_of_host (public) rp_filter->acs::root_of_host acs_magic_object acs_magic_object (public) rp_filter->acs_magic_object ad_acs_kernel_id ad_acs_kernel_id (public) rp_filter->ad_acs_kernel_id ad_conn ad_conn (public) rp_filter->ad_conn ad_host ad_host (public) rp_filter->ad_host

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 $result
XQL Not present:
Generic, PostgreSQL, Oracle
[ hide source ] | [ make this the default ]
Show another procedure: