Index: openacs-4/packages/acs-automated-testing/tcl/aa-test-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-automated-testing/tcl/aa-test-procs.tcl,v diff -u -r1.79.2.69 -r1.79.2.70 --- openacs-4/packages/acs-automated-testing/tcl/aa-test-procs.tcl 26 Feb 2024 09:51:59 -0000 1.79.2.69 +++ openacs-4/packages/acs-automated-testing/tcl/aa-test-procs.tcl 5 Jul 2024 13:43:29 -0000 1.79.2.70 @@ -1330,10 +1330,10 @@ #aa_log "HTTP: user_info [ns_quotehtml <$user_info>]" #aa_log "HTTP: start session_info [ns_quotehtml <$session>]" - set url [acs::test::url] - set urlInfo [ns_parseurl $url] + set test_url [acs::test::url] + set urlInfo [ns_parseurl $test_url] set address [dict get $urlInfo host] - set url "$url/$request" + set url ${test_url}/${request} # # Either authenticate via user_info (when specified) or via @@ -1404,6 +1404,17 @@ if {![string match "3??" $status] || $location eq ""} { break } + + # + # According to + # https://www.rfc-editor.org/rfc/rfc7231#section-7.1.2, + # the location header may return a relative URL as + # well. + # + set location [util::complete_location \ + -location $location \ + -complete_url $test_url] + } } finally { # Index: openacs-4/packages/acs-tcl/tcl/http-client-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-tcl/tcl/http-client-procs.tcl,v diff -u -r1.30.2.29 -r1.30.2.30 --- openacs-4/packages/acs-tcl/tcl/http-client-procs.tcl 25 Feb 2024 16:13:42 -0000 1.30.2.29 +++ openacs-4/packages/acs-tcl/tcl/http-client-procs.tcl 5 Jul 2024 13:43:26 -0000 1.30.2.30 @@ -1128,6 +1128,15 @@ set location ${location}?${urlvars} } + # + # According to + # https://www.rfc-editor.org/rfc/rfc7231#section-7.1.2, the + # location header may return a relative URL as well. + # + set location [util::complete_location \ + -location $location \ + -complete_url $url] + if {$method eq "GET"} { return [$this_proc \ -method GET \ @@ -1494,7 +1503,6 @@ set r_headers [ns_set array $resp_headers] ns_set free $resp_headers - # Redirection handling if {$depth < $max_depth} { incr depth Index: openacs-4/packages/acs-tcl/tcl/utilities-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-tcl/tcl/utilities-procs.tcl,v diff -u -r1.189.2.175 -r1.189.2.176 --- openacs-4/packages/acs-tcl/tcl/utilities-procs.tcl 3 Jun 2024 17:47:19 -0000 1.189.2.175 +++ openacs-4/packages/acs-tcl/tcl/utilities-procs.tcl 5 Jul 2024 13:43:29 -0000 1.189.2.176 @@ -1964,6 +1964,51 @@ return $result } +ad_proc util::complete_location { + -location:required + -complete_url +} { + Completes specified relative URL. When no complete URL is + specified for reference, it will assume the current host as the + server. + + The purpose of this utility is to be used by HTTP clients to + complete URLs coming from the Location response header in case of + a redirect, which according to RFC 7231 may also be relative. + + @param location a supposedly relative URL to complete. When the + URL is already complete, it will be returned + as-is. + @param complete_url URL of the redirected request. A complete URL + from which we want to read the host. When + missing, the URL will be completed using the + result of util_current_location. + + @return a complete absolute URL + + @see https://www.rfc-editor.org/rfc/rfc7231#section-7.1.2 +} { + if {![info exists complete_url]} { + set complete_url [util_current_location] + } elseif {![dict exists [ns_parseurl -strict $complete_url] host]} { + error "util::complete_location: '$complete_url' is not a valid complete URL" + } + + set parsed_location [ns_parseurl -strict $location] + if {[dict exists $parsed_location host]} { + return $location + } + + util::split_location $complete_url proto hostname port + + set host_url [util::join_location \ + -proto $proto \ + -hostname $hostname \ + -port $port] + + return [ns_absoluteurl $location $host_url] +} + ad_proc -public util::configured_location {{-suppress_port:boolean}} { Return the configured location as configured for the current Index: openacs-4/packages/acs-tcl/tcl/test/utilities-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-tcl/tcl/test/Attic/utilities-procs.tcl,v diff -u -r1.1.2.20 -r1.1.2.21 --- openacs-4/packages/acs-tcl/tcl/test/utilities-procs.tcl 21 Apr 2023 16:15:46 -0000 1.1.2.20 +++ openacs-4/packages/acs-tcl/tcl/test/utilities-procs.tcl 5 Jul 2024 13:43:29 -0000 1.1.2.21 @@ -440,6 +440,106 @@ api production_safe } -procs { + util::complete_location +} util__complete_url { + Test util::complete_location +} { + aa_equals "Basic case" \ + [string trimright [util::complete_location -location ""] /] \ + [util_current_location] + + aa_equals "Complete with current location" \ + [string trimright [util::complete_location -location "/a/b/c"] /] \ + [util_current_location]/a/b/c \ + + aa_equals "Complete an already complete location (normie case)" \ + [util::complete_location -location "https://example.com/a/b/c"] \ + "https://example.com/a/b/c" \ + + aa_equals "Complete an already complete location (protocol relative)" \ + [util::complete_location -location "//a/b/c"] \ + //a/b/c \ + + aa_true "Complete an invalid location (relative) - Should fail" \ + [catch { + util::complete_location -location "/file\[/\].html" + }] + + aa_true "Complete an invalid location (absolute) - Should fail" \ + [catch { + util::complete_location -location "http://example.com/file\[/\].html" + }] + + aa_equals "Basic case with external location" \ + [string trimright [util::complete_location \ + -complete_url "http://example.com" \ + -location ""] /] \ + http://example.com + + aa_equals "Basic case with external location (complete_url has a path)" \ + [string trimright [util::complete_location \ + -complete_url "http://example.com/a/b/c" \ + -location ""] /] \ + http://example.com + + aa_equals "Complete with external location (complete_url just the host)" \ + [string trimright [util::complete_location \ + -complete_url "http://example.com" \ + -location "/a/b/c"] /] \ + http://example.com/a/b/c \ + + aa_equals "Complete with external location (complete_url with a path)" \ + [string trimright [util::complete_location \ + -complete_url "http://example.com/d/e/f" \ + -location "/a/b/c"] /] \ + http://example.com/a/b/c \ + + aa_equals "Complete an already complete location (normie case)" \ + [util::complete_location \ + -complete_url "http://anotherexample.com/d/e/f" \ + -location "https://example.com/a/b/c"] \ + "https://example.com/a/b/c" \ + + aa_equals "Complete an already complete location (protocol relative)" \ + [util::complete_location \ + -complete_url "http://anotherexample.com/d/e/f" \ + -location "//a/b/c"] \ + //a/b/c \ + + aa_true "Complete an invalid location (relative) - Should fail" \ + [catch { + util::complete_location \ + -complete_url "http://example.com/d/e/f" \ + -location "/file\[/\].html" + }] + + aa_true "Complete an invalid location (absolute) - Should fail" \ + [catch { + util::complete_location \ + -complete_url "http://example.com/d/e/f" \ + -location "http://example.com/file\[/\].html" + }] + + aa_true "Complete with an invalid complete_url - Should fail" \ + [catch { + util::complete_location \ + -complete_url "http://example.com/file\[/\].html" \ + -location "/file/a/b" + }] + + aa_true "Complete with a relative complete_url - Should fail" \ + [catch { + util::complete_location \ + -complete_url "/c/d/e" \ + -location "/file/a/b" + }] +} + + +aa_register_case -cats { + api + production_safe +} -procs { util::file_content_check } util__file_content_check { Test util::file_content_check.