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
homeorsiteurluses a non-standard port (e.g.http://example.com:8080,http://localhost:8888, Docker, staging)
## Steps to reproduce
- Set
homeandsiteurlto a URL with a non-standard port:http://example.com:8080 - Call
get_allowed_http_origins(). - Observe the returned list contains
http://example.com(no port). - Send a CORS request from the browser with
Origin: http://example.com:8080. - Call
is_allowed_http_origin( 'http://example.com:8080' ).
## Expected result
get_allowed_http_origins()includeshttp://example.com:8080andhttps://example.com:8080.is_allowed_http_origin( 'http://example.com:8080' )returnshttp://example.com:8080.
## Actual result
- Allowed list contains
http://example.comwithout 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
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
:8889install), identifying the default-port edge case missed by the existing PR, and drafting the change and PHPUnit tests. All changes were reviewed, reproduced in thewordpress-developDocker environment, and verified with PHPUnit and PHPCS by me.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)
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 browserOriginheader includes the port while the allowed list did not.Adds PHPUnit test coverage in
tests/phpunit/tests/http/getAllowedHttpOrigins.php.