Skip to content

Replace manual URL construction from $_SERVER['HTTP_HOST'] with network_home_url().#10939

Open
ootwch wants to merge 6 commits intoWordPress:6.9from
ootwch:20260216F_wp69_rebased
Open

Replace manual URL construction from $_SERVER['HTTP_HOST'] with network_home_url().#10939
ootwch wants to merge 6 commits intoWordPress:6.9from
ootwch:20260216F_wp69_rebased

Conversation

@ootwch
Copy link

@ootwch ootwch commented Feb 16, 2026

Replace manual URL construction from $_SERVER['HTTP_HOST'] with network_home_url()

Several places in WordPress core build absolute URLs by concatenating raw $_SERVER['HTTP_HOST'] with $_SERVER['REQUEST_URI']:

$url = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
// or
$url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );

This breaks when WordPress is behind a reverse proxy that rewrites paths — HTTP_HOST reflects the proxy's internal hostname and REQUEST_URI may have had a path prefix stripped. WordPress already provides network_home_url() which reads the canonical URL from the database and handles scheme, host, and path prefix correctly. This PR replaces every URL-construction use of HTTP_HOST with the appropriate WordPress URL function.

Files changed (11 files, net −24 lines):

File What changed
wp-admin/includes/class-wp-list-table.php 2× pagination / column-sort current URL
wp-admin/includes/misc.php wp_admin_canonical_url()
wp-includes/admin-bar.php Customizer menu current URL
wp-includes/blocks/loginout.php Login/logout block redirect URL
wp-includes/canonical.php redirect_canonical() fallback URL
wp-includes/class-wp-recovery-mode.php Recovery-mode redirect (also removes dead $scheme variable)
wp-includes/functions.php wp_auth_check_html() domain comparison
wp-includes/general-template.php wp_login_form() default redirect
wp-includes/nav-menu-template.php Custom menu-item active-state matching
wp-includes/pluggable.php auth_redirect() — 3 SSL redirects + login redirect; also wp_redirect()wp_safe_redirect()
wp-login.php SSL force-redirect

Intentionally unchanged HTTP_HOST usages:

  • wp-includes/media.php wp_calculate_image_srcset() — domain-identity check (not URL construction)
  • wp-login.php RELOCATE block — deliberately reads the actual host to update siteurl

Trac ticket: https://core.trac.wordpress.org/ticket/53998

Use of AI Tools

This PR was developed with the assistance of Cursor IDE with Claude. The AI was used for:

  • Verifying that all instances of $_SERVER['HTTP_HOST'] used for URL construction in core are considered
  • Analysing which instances are URL construction (replaced) vs. domain comparison/discovery (kept)
  • Drafting the replacement code and resolving rebase conflicts from 6.8 → 6.9
  • Generating the text of this request

All changes were reviewed, tested, and approved by the author. Most of these changes have already been done in 2022.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

@github-actions
Copy link

github-actions bot commented Feb 16, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @=, @ootwch.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

Core Committers: Use this line as a base for the props when committing in SVN:

Props westonruter.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@ootwch ootwch force-pushed the 20260216F_wp69_rebased branch from 0ee2fcd to 8908e8b Compare February 16, 2026 05:57
@westonruter
Copy link
Member

I don't believe this will work as intended. For example, the $_SERVER['REQUEST_URI'] includes the request's current full path. Nevertheless, network_home_url() will also prepend a path for the network:

$url = set_url_scheme( 'http://' . $current_network->domain . $current_network->path, $scheme );

If the network doesn't exist at the root, then this would result in duplicated path segment.

Replace manual URL construction from $_SERVER['HTTP_HOST'] with
network_home_url() across 11 core files. This fixes WordPress behind
reverse proxies that rewrite paths (e.g. branch-based staging).

Also upgrades wp_redirect() to wp_safe_redirect() in auth_redirect()
and removes a dead $scheme variable in class-wp-recovery-mode.php.

Trac ticket: https://core.trac.wordpress.org/ticket/53998
@ootwch ootwch force-pushed the 20260216F_wp69_rebased branch from 8908e8b to 847045c Compare February 16, 2026 06:30
@ootwch
Copy link
Author

ootwch commented Feb 16, 2026

I don't believe this will work as intended. For example, the $_SERVER['REQUEST_URI'] includes the request's current full path. Nevertheless, network_home_url() will also prepend a path for the network:

$url = set_url_scheme( 'http://' . $current_network->domain . $current_network->path, $scheme );

If the network doesn't exist at the root, then this would result in duplicated path segment.

Understood, I think. In my setup (CI dev environment, not production) that does not seem to happen because the reverse proxy corrects this.

Cursor recommends a dedicated utility function to fix this; I can adapt the CR and verify this in my own setup, but this is then more than than the "just fix a couple of lines" change I had originally intended.. and my tests will not be encompassing enough.

Still, I think the original issue still exists (and has existed for a long time); would be nice to get some improvements for this into core, perhaps.

/**
 * Builds a full URL for the current request using the site's configured host.
 *
 * Replaces raw $_SERVER['HTTP_HOST'] with the host from home_url(), which
 * respects the DB-configured site address. This is important behind reverse
 * proxies or load balancers where HTTP_HOST may not match the public host.
 *
 * If $_SERVER['REQUEST_URI'] already contains the home path (standard setup),
 * only the scheme and host are swapped. If the home path is missing from
 * REQUEST_URI (e.g. a reverse proxy stripped a path prefix), the home path
 * is prepended automatically.
 *
 * @since 6.9.0
 *
 * @return string Full URL of the current request.
 */
function wp_get_current_request_url() {
    $home      = home_url( '/' );
    $home_path = wp_parse_url( $home, PHP_URL_PATH ) ?: '/';

    if ( str_starts_with( $_SERVER['REQUEST_URI'], $home_path ) ) {
        // Standard case: REQUEST_URI already includes the home path.
        // Just replace scheme + host, keep REQUEST_URI as-is.
        $parsed = wp_parse_url( $home );
        $host   = $parsed['host'] ?? $_SERVER['HTTP_HOST'];
        if ( isset( $parsed['port'] ) ) {
            $host .= ':' . $parsed['port'];
        }
        return set_url_scheme( 'http://' . $host . $_SERVER['REQUEST_URI'] );
    }

    // Reverse-proxy case: REQUEST_URI is missing the home path prefix.
    // Let home_url() prepend it.
    return home_url( $_SERVER['REQUEST_URI'] );
}

@westonruter
Copy link
Member

What about the get_self_link() function?

Kurt Gysin and others added 5 commits February 16, 2026 20:34
**Built by cursorAI - I can (and will) run my test suite later,
but I do not have the knowledge to identify side effects. **

Tested only for the case where the install is behind a reverse proxy.

Introduces wp_get_current_request_url() in link-template.php, a helper
that builds the current request URL using the DB-configured host from
home_url() instead of raw $_SERVER['HTTP_HOST'].

The helper detects whether REQUEST_URI already includes the home path
(standard setup — just swaps scheme+host) or is missing it (reverse
proxy stripped a prefix — prepends via home_url()).

All call sites that previously concatenated HTTP_HOST + REQUEST_URI
now use this helper. SSL-forcing redirects (wp-login.php, pluggable.php)
explicitly wrap with set_url_scheme(..., 'https') to preserve the
upstream guarantee of HTTPS enforcement.

See https://core.trac.wordpress.org/ticket/53998
get_self_link() in feed.php was a feed-specific predecessor of the same
pattern centralised in wp_get_current_request_url(). It already avoided
$_SERVER['HTTP_HOST'] by taking the host from home_url(), but it still
concatenated the raw $_SERVER['REQUEST_URI'] directly — so behind a
path-stripping reverse proxy the branch/subdirectory prefix was lost.

Replace the manual host-extraction + concatenation with a single call to
wp_get_current_request_url(), which handles both the standard case and the
reverse-proxy case (missing path prefix) correctly.

See https://core.trac.wordpress.org/ticket/53998

Co-authored-by: Claude (Anthropic) <noreply@anthropic.com>
wp_referer_field() outputs a hidden form field containing the current
request URL so that form handlers can redirect back to the originating
page.  It called remove_query_arg() without an explicit URL, which
defaults to the raw $_SERVER['REQUEST_URI'].

Behind a path-stripping reverse proxy, REQUEST_URI is missing the
site's path prefix.  For example on a subdirectory install served
under /subdir/:

  Expected:  /subdir/my-account/
  Actual:    /my-account/

After login, code that reads this value via wp_get_raw_referer()
redirects the browser to /my-account/ — a path that does not exist
on the proxy because the subdirectory prefix is missing.

Fix: pass wp_get_current_request_url() as the base URL to
remove_query_arg().  This produces a full absolute URL that includes
the home path from the DB-configured site address, so the redirect
works correctly in both standard installs and behind reverse proxies.

See https://core.trac.wordpress.org/ticket/53998

Co-authored-by: Claude (Anthropic) <noreply@anthropic.com>
… call sites.

Add unit tests covering the new wp_get_current_request_url() helper
and the behavioral changes in wp_referer_field() and get_self_link().

New test files:
- tests/phpunit/tests/link/wpGetCurrentRequestUrl.php
  15 tests covering root installs, subdirectory installs, reverse-proxy
  prefix stripping, explicit $request_uri parameter, scheme detection,
  host resolution from home_url(), port preservation, and query strings.

- tests/phpunit/tests/feed/getSelfLink.php
  6 tests verifying get_self_link() delegates to
  wp_get_current_request_url() correctly across standard and
  reverse-proxy configurations.

Updated test file:
- tests/phpunit/tests/functions/wpRefererField.php
  Adjusted existing tests for the new absolute-URL behavior and added
  tests for subdirectory-aware referer values and query-arg stripping.

See #53998.
… call sites.

Add unit tests covering the new wp_get_current_request_url() helper
and the behavioral changes in wp_referer_field() and get_self_link().

New test files:
- tests/phpunit/tests/link/wpGetCurrentRequestUrl.php
  15 tests covering root installs, subdirectory installs, reverse-proxy
  prefix stripping, explicit $request_uri parameter, scheme detection,
  host resolution from home_url(), port preservation, and query strings.

- tests/phpunit/tests/feed/getSelfLink.php
  6 tests verifying get_self_link() delegates to
  wp_get_current_request_url() correctly across standard and
  reverse-proxy configurations.

Updated test file:
- tests/phpunit/tests/functions/wpRefererField.php
  Adjusted existing tests for the new absolute-URL behavior and added
  tests for subdirectory-aware referer values and query-arg stripping.

See #53998.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments