Skip to content

HTTP: Preserve non-default port in get_allowed_http_origins()#12338

Open
dhrupo wants to merge 1 commit into
WordPress:trunkfrom
dhrupo:ticket-65522-cors-port
Open

HTTP: Preserve non-default port in get_allowed_http_origins()#12338
dhrupo wants to merge 1 commit into
WordPress:trunkfrom
dhrupo:ticket-65522-cors-port

Conversation

@dhrupo

@dhrupo dhrupo commented Jun 27, 2026

Copy link
Copy Markdown

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.

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

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.

get_allowed_http_origins() built the allowed CORS origin list from the
host of admin_url() and home_url() only, dropping any port (there was a
'@todo Preserve port?' note). Sites served on a non-default port, such as
http://example.com:8080, had their cross-origin requests rejected because
the browser Origin header includes the port while the allowed list did not.

Append the port to the allowed origins when it is present and non-default.
The default HTTP and HTTPS ports (80 and 443) are 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.

Adds regression tests covering custom ports, the default-port case, a
port-less URL, and is_allowed_http_origin() matching a custom-port origin.

Fixes #65522.
@github-actions

Copy link
Copy Markdown

Hi there! 👋

Thank you for your contribution to WordPress! 💖

It looks like this is your first pull request to wordpress-develop. Here are a few things to be aware of that may help you out!

No one monitors this repository for new pull requests. Pull requests must be attached to a Trac ticket to be considered for inclusion in WordPress Core. To attach a pull request to a Trac ticket, please include the ticket's full URL in your pull request description.

Pull requests are never merged on GitHub. The WordPress codebase continues to be managed through the SVN repository that this GitHub repository mirrors. Please feel free to open pull requests to work on any contribution you are making.

More information about how GitHub pull requests can be used to contribute to WordPress can be found in the Core Handbook.

Please include automated tests. Including tests in your pull request is one way to help your patch be considered faster. To learn about WordPress' test suites, visit the Automated Testing page in the handbook.

If you have not had a chance, please review the Contribute with Code page in the WordPress Core Handbook.

The Developer Hub also documents the various coding standards that are followed:

Thank you,
The WordPress Project

@github-actions

Copy link
Copy Markdown

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.

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

Props dhrupo.

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

@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@apermo apermo left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

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