Repository navigation
Conversation
…n KSES. `wp_kses_bad_protocol_once()` treats everything before the first colon as a protocol, unless that part contains the literal sequence `/?`. Browsers end a URL scheme at the first `/`, `?` or `#`, so relative URLs such as `#fn:1`, `/page#section:2` or `images?q=tbn:abc` were cut down to the text after the colon. Users without the `unfiltered_html` capability lost these links when saving content, and `esc_url()` returned an empty string for them. A colon is no longer treated as a protocol separator when a `/`, `?` or `#` comes before it. A `#` directly after `&` or `&` is not counted, since it may belong to a character reference. Props bogdanpreda, dmsnell. Fixes #41304.
|
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 Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe 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
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
|
Thanks for working on this, @dhrupo! The table of before/after inputs in the description made it easy to check against browser parsing rules. I tested it locally: Patch Testing ReportPatch tested: #14320 Environment
Steps taken
Expected result
Additional Notes
|
Trac ticket: https://core.trac.wordpress.org/ticket/41304
Summary
wp_kses_bad_protocol_once()treats everything before the first colon of a URL as its protocol. The only exception is when that part contains the literal two-character sequence/?.Browsers end a scheme at the first
/,?or#(a scheme may only contain letters, digits,+,-and.), so a colon after any of those characters can never separate a protocol. KSES still strips the text before it:#fn:1(Markdown footnote)1#fn:1/page#section:22/page#section:2images?q=tbn:abc(the example in the ticket)abcimages?q=tbn:abc#:~:text=highlight(text fragment, #60347)text=highlight#:~:text=highlight//example.org:8080/8080///example.org:8080/As a result, users without
unfiltered_htmllose these links when they save a post, andesc_url()returns''for them.Reproduction
Log in as an Author, and in the code editor save a post containing:
Reload the editor. The saved content is
<a href="1">and<a href="abc">. Before and after screenshots of this run are attached on the Trac ticket.Fix
One line in
wp_kses_bad_protocol_once(). The'%/\?%'check becomes'%[/?]|(?<!&)(?<!&)#%': a colon is not a protocol separator when a/,?or#comes before it.#directly after&or&is not counted, because it may belong to a character reference (javascript:stays stripped, and so does the existing&#14; javascript:test case).Behaviour changes worth noting
esc_url(),esc_url_raw()andsanitize_url()now return these URLs instead of''. This includes scheme-less URLs such aswww.example.com/wiki/Help:Contents, which are now kept as relative links. Without a colon, that URL getshttp://prepended. Aligning the prepend check is a possible follow-up, deliberately not bundled here.wp_http_validate_url( '//example.org:8080/' )now returns the URL, as//example.org/already does. Private and loopback hosts are still rejected, andWP_Http::request()still refuses URLs without a scheme.#:~:text=to be stripped when onlymailtois allowed; here a fragment is kept regardless of the allowed protocols, the same as#footerand//host/are today. Details are on the ticket.Testing
New tests (
@ticket 41304):Tests_Kses: data providers for URLs that must be kept and for protocols that must still be stripped, pluswp_kses_post()cases.Tests_Formatting_EscUrl::test_colon_after_fragment_or_query.Before the fix, the "kept" cases, the
wp_kses_post()footnote case and theesc_url()case fail; the "still stripped" cases pass both before and after. After the fix:No failures. The 5 warnings are existing PHPUnit 10 deprecation notices in unrelated tests (
Tests_Formatting_wpStripAllTags,Tests_HTTP_IncludeOldRequestsClass).Security checks beyond the unit tests:
/ ? #,&#,&, tabs and control characters found 98,391 changed outputs and 0 dangerous ones.'%[/?#]%'(no entity guard) and against a guard without&.Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: Tracing the protocol check and its callers, writing the fix and tests, and running the browser and fuzz checks. All changes and results were reviewed by me.
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.