Make WordPress Core

Opened 8 weeks ago

Last modified 8 days ago

#65522 new defect (bug)

get_allowed_http_origins() drops port from allowed CORS origins

Reported by: yaghoot Owned by:
Priority: normal Milestone: Awaiting Review
Component: HTTP API Version: 3.4
Severity: normal Keywords: has-patch has-unit-tests
Cc: Focuses:

Description

## Environment

  • WordPress: trunk (latest development version)
  • Any install where home or siteurl uses a non-standard port (e.g. http://example.com:8080, http://localhost:8888, Docker, staging)

## Steps to reproduce

  1. Set home and siteurl to a URL with a non-standard port: http://example.com:8080
  2. Call get_allowed_http_origins().
  3. Observe the returned list contains http://example.com (no port).
  4. Send a CORS request from the browser with Origin: http://example.com:8080.
  5. Call is_allowed_http_origin( 'http://example.com:8080' ).

## Expected result

  • get_allowed_http_origins() includes http://example.com:8080 and https://example.com:8080.
  • is_allowed_http_origin( 'http://example.com:8080' ) returns http://example.com:8080.

## Actual result

  • Allowed list contains http://example.com without the port.
  • is_allowed_http_origin( 'http://example.com:8080' ) returns '' (silently rejected).
  • CORS requests from the same host on a non-standard port fail with no visible error.

## Root cause

get_allowed_http_origins() in src/wp-includes/http.php calls
parse_url() on admin_url() and home_url() but only uses the
host key, discarding port. There is an existing @todo Preserve port?
comment at the affected location.

## Proposed fix

Append the port to the host when present:

`php
$admin_host = $admin_originhost . ( isset( $admin_originport ) ? ':' . $admin_originport : );
$home_host = $home_originhost . ( isset( $home_originport ) ? ':' . $home_originport :
);

Test plan

Add tests/phpunit/tests/http/getAllowedHttpOrigins.php with 5 tests:

  • Non-standard port preserved in allowed list
  • Standard URLs produce no :80 suffix
  • Different ports on home vs admin both preserved
  • is_allowed_http_origin() accepts origin with matching port
  • is_allowed_http_origin() rejects origin without port when site uses one

Verified locally: 4 failures without fix, 5 passes with fix.

Change History (3)

This ticket was mentioned in PR #12287 on WordPress/wordpress-develop by @yaghoot.


8 weeks ago
#1

  • Keywords has-patch has-unit-tests added

https://core.trac.wordpress.org/ticket/65522

get_allowed_http_origins() discarded the port from admin and home URLs when building the allowed CORS origin list. Sites on non-standard ports (e.g. http://example.com:8080) had CORS requests silently rejected because the browser Origin header includes the port while the allowed list did not.

Adds PHPUnit test coverage in tests/phpunit/tests/http/getAllowedHttpOrigins.php.

This ticket was mentioned in PR #12338 on WordPress/wordpress-develop by @dhrupo.


8 weeks ago
#2

## Summary

Preserve a non-default port in get_allowed_http_origins() so that sites served on a custom port (for example http://example.com:8080, or a local http://localhost:8889) are matched against the browser Origin request header.

## Problem

get_allowed_http_origins() built the allowed origin list from the host of admin_url() and home_url() only, discarding the port (there was even a // @todo Preserve port? note in core):

$admin_origin = parse_url( admin_url() );
// @todo Preserve port?
$allowed_origins = array_unique( array(
    'http://' . $admin_origin['host'],
    ...
) );

For a site on http://localhost:8889, this returns http://localhost / https://localhost. The browser sends Origin: http://localhost:8889, which is not in the list, so the cross-origin request is rejected.

## Solution

Append the port when it is present and non-default. The default HTTP and HTTPS ports (80 and 443) are intentionally omitted, because browsers leave them out of the Origin header — so an explicit :80/:443 in the site URL still produces a port-less origin that matches the request.

## Relationship to #12287

There is an existing PR, #12287, which also appends the port. This PR improves on it in one important way: #12287 appends the port unconditionally, including the default ports. For a site whose siteurl is stored as http://example.com:80, #12287 produces only http://example.com:80 and no port-less variant. Since the browser sends Origin: http://example.com (port 80 omitted), that case is broken by #12287 — it would reject a request that currently works on trunk. This PR omits the default ports, so both the custom-port case and the explicit-default-port case are correct. The included tests cover the default-port case explicitly.

## Testing

Verified on a local install served at http://localhost:8889:

// Before: array( 'http://localhost', 'https://localhost' )
// After:  array( 'http://localhost:8889', 'https://localhost:8889' )
get_allowed_http_origins();

Automated:

phpunit tests/phpunit/tests/http/getAllowedHttpOrigins.php

New tests cover a custom port, a port-less URL, the explicit default-port case (:80/:443), and is_allowed_http_origin() matching a custom-port origin.

## Use of AI Tools

  • AI assistance: Yes
  • Tool(s): Claude Code
  • Model(s): Claude Opus 4.8
  • Used for: Reproducing the issue (on a local :8889 install), identifying the default-port edge case missed by the existing PR, and drafting the change and PHPUnit tests. All changes were reviewed, reproduced in the wordpress-develop Docker environment, and verified with PHPUnit and PHPCS by me.

#3 @wildworks
8 days ago

  • Version trunk3.4

Thanks for the report. This issue appears to have been present since WP 3.4.0, when the get_allowed_http_origins() function was added. See r20794

Note: See TracTickets for help on using tickets.