[wp-trac] [WordPress Trac] #45459: Infinite Loop By redirect_canonical (canonical.php) due to mishandling of port in URL

WordPress Trac noreply at wordpress.org
Sun Dec 2 05:14:24 UTC 2018


#45459: Infinite Loop By redirect_canonical (canonical.php) due to mishandling of
port in URL
--------------------------+-----------------------------
 Reporter:  dkristanda    |      Owner:  (none)
     Type:  defect (bug)  |     Status:  new
 Priority:  normal        |  Milestone:  Awaiting Review
Component:  Canonical     |    Version:  4.9.8
 Severity:  major         |   Keywords:
  Focuses:                |
--------------------------+-----------------------------
 **Background**

 Fresh install version 4.9.8 for a simple website (no SSL, using standard
 theme twentyseventeen). Standard installation went well without any
 hiccup.
 However immediately after installation "Too many redirect errors" occurred
 using any browser.

 **Infinite loop Symptoms**
 * Happen on URL that required index.php e.g: http://[domain.com] ,
 http://[domain.com]/ (with or without trailing slash)
 * Full permalink URL such as:
   * http://[domain.com]/2018/12/01/hello-world/
   * http://[domain.com]/category/uncategorised/
   will render without any problem
 This symptom actually is actually has happened before in this particular
 server and all my other website on this server requiring the removal of
 redirect_canonical function.

 Applied in functions.php of the themes file as follow:
 {{{
 remove_filter('template_redirect', 'redirect_canonical');
 }}}

 With the removal of redirect_canonical above, the infinite loop is
 mitigated. But I decided this time to try to pin out the real problem
 because removing canonicalization is not a very good thing for SEO.

 **Investigation**
 * This fresh install infinite loop happen in my server that uses "reverse
 proxy" setting using Nginx, i.e the requested url is passed around between
 reverse proxy and upstream server using http://[domain.com]:80 and
 http://[domain.com]:8080 (or any other arbitrary port)

 * Trace the problem to canonical.php and catch the bug as follows
 * Original snippet of canonical.php (in wp-includes)
 {{{
 530         if ( $do_redirect ) {
 531                 // protect against chained redirects
 532                 if ( !redirect_canonical($redirect_url, false) ) {
 533                         wp_redirect($redirect_url, 301);
 534                         exit();
 535                 } else {
 536                         // Debug
 537                         // die("1: $redirect_url<br />2: " .
 redirect_canonical( $redirect_url, false ) );
 538                         return;
 539                 }
 540         } else {
 541                 return $redirect_url;
 542         }

 }}}

 * Minor modification to catch the bug (reverse the logic in line 532 so it
 goes to the debug section and add $requested_url to be printed as well):
 {{{
 530         if ( $do_redirect ) {
 531                 // protect against chained redirects
 532                 if ( redirect_canonical($redirect_url, false) ) {
 533                         wp_redirect($redirect_url, 301);
 534                         exit();
 535                 } else {
 536                         // Debug
 537                         die("1: $redirect_url<br />2:
 $requested_url<br />3: " . redirect_canonical( $redirect_url, false ) );
 538                         return;
 539                 }
 540         } else {
 541                 return $redirect_url;
 542         }

 }}}

 * From the print out of the debugging, it is confirmed that the
 differences between the $requested_url and $redirect_url is causing the
 infinite loop: (i.e: the port ":80" is missing in the $redirect_url,
 causing it to recursively redirected to the same http://[domain.com]/)
   * $redirect_url: http://[domain.com]/
   * $requested_url: http://[domain.com]:80/

 If the url were http://[domain.com]/2018/12/01/hello-world/ it will not
 produce the infinite loop as the result of the debugging is:
   * $redirect_url: http://[domain.com]/2018/12/01/hello-world/
   * $requested_url: http://[domain.com]/2018/12/01/hello-world/

 (no port number produced by the function)

 * Trace further how the function handle port, and found this "lazy"
 snippet (original):
 {{{
 386         // Handle ports
 387         if ( !empty($user_home['port']) )
 388                 $redirect['port'] = $user_home['port'];
 389         else
 390                 unset($redirect['port']);
 391
 }}}

 Apparently from the code above, if the $WP_HOME doesn't have port
 component it just removes the component for redirection. Seems a bit lazy
 to me.

 True enough, I add below line in wp-config.php and fix the problem
 {{{
 define('WP_HOME','http://[domain.com]:80');
 }}}
 But of course, it is never that simple. While the infinite loop for the
 home page is handled, the other link is affected. In this case, going back
 to the debuging mode, the result os requesting url
 http://[domain.com]/2018/12/01/hello-world/ produced:
   * $redirect_url: http://[domain.com]:80/2018/12/01/hello-world/
   * $requested_url: http://[domain.com]/2018/12/01/hello-world/

 (i.e: the port ":80" is now added on the $redirect_url)

 * Trace further up as the source of the port, it seems that it come from
 $original[] variable within the function
 {{{
  70         $original = @parse_url($requested_url);
 }}}

 which sometimes have port information parsed, but sometimes is not.

 **TEMPORARY SOLUTION (so far okay)**
 The idea is how the code could handle "port" better:
 * if there is no port information in $original variable, why bother adding
 port info to the redirection
 * but if there is one, the redirection needs to also include the port info
 to prevent mismatch (hence infinite loop) i.e http://[domain.com]:80/
 redirect to http://[domain.com]/ which then redirect to
 http://[domain.com]:80/ and so on.

 Here is the code that I come up that solve this issue so far
 {{{
 386         // Handle ports
 387         if ( isset($original['port']) && !empty($original['port'])) {
 388              if ( !empty($user_home['port']) )
 389                   $redirect['port'] = $user_home['port'];
 390              else {
 391                   $redirect['port']='443';
 392                   if (isset($original['scheme']) &&
 !strcmp($original['scheme'],'http')) $redirect['port']='80';
 393              }
 394          }
 }}}
 i.e: if user is using exoctic/unusual port they need to put it on the
 $WP_HOME anyway - otherwise assuming 80 and 443 would be quite common.

 What causing the port number to come up? I assume it is due to the reverse
 proxy configuration in 1 server where parameter passing is using the port
 number that is not common. (e.g between https:443 to http:8080), but
 nevertheless, the more proper handling of port info as described above
 should be logical and necessary to prevent the unnecessary infinite loop.

 I categorize this bug initially as "major" because it happens without any
 variety of user data, it is from a fresh install and reverse-proxy caching
 is a common technique used even in a shared hosting environment.
 The expert at WordPress can determine whether the solution is robust
 enough to be implemented in more permanently.

 So, with modification of that, people that use reverse proxy setting can
 still use canonicalization feature from WordPress without infinite loop
 problem. Also they can remove:
 {{{
 remove_filter('template_redirect', 'redirect_canonical');
 }}}
 from their functions.php

 Thanks, hope this helps.

-- 
Ticket URL: <https://core.trac.wordpress.org/ticket/45459>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list