Skip to content

Commit 73e2bad

Browse files
nicolas-grekasLamentXU123
authored andcommitted
Fix glob() not negating bracket expressions that start with "^" (#24422)
Since PHP 8.5, glob() uses the bundled implementation on all platforms. It only recognizes a leading "!" as bracket negation, so patterns such as "[^a]" match "^" or "a" instead of excluding "a", unlike glibc's glob(). Treat a leading "^" like "!", matching glibc and PHP's fnmatch() behavior, including on Windows. POSIX leaves this syntax unspecified. Document the intentional deviation from OpenBSD so it is preserved during future syncs. Add regression coverage for negated characters and ranges, a leading "]", and a literal "^" elsewhere in a bracket expression. This was found while working on symfony/symfony#67133. Closes #24422
1 parent 62c3ea2 commit 73e2bad

3 files changed

Lines changed: 39 additions & 0 deletions

File tree

‎NEWS‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ PHP NEWS
106106
(Nicolas Grekas)
107107
. Fixed bug GH-24399 (usleep() and time_nanosleep() ignore queued APCs on
108108
Windows). (Nicolas Grekas)
109+
. Fixed glob() not negating bracket expressions that start with "^".
110+
(Nicolas Grekas)
109111

110112
- Tidy:
111113
. Fixed tidy_repair_string(), tidy_repair_file(), tidy::repairString() and
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
glob() negates a bracket expression that starts with "!" or "^"
3+
--FILE--
4+
<?php
5+
$dir = __DIR__ . '/glob_bracket_negation';
6+
@mkdir($dir);
7+
foreach (['a', 'b', 'c', '^', '!'] as $name) {
8+
touch("$dir/$name");
9+
}
10+
11+
foreach (['[!a]', '[^a]', '[!a-b]', '[^a-b]', '[!]a]', '[^]a]', '[a^]'] as $pattern) {
12+
echo $pattern, ': ', implode(' ', array_map('basename', glob("$dir/$pattern"))), "\n";
13+
}
14+
?>
15+
--CLEAN--
16+
<?php
17+
$dir = __DIR__ . '/glob_bracket_negation';
18+
foreach (['a', 'b', 'c', '^', '!'] as $name) {
19+
@unlink("$dir/$name");
20+
}
21+
@rmdir($dir);
22+
?>
23+
--EXPECT--
24+
[!a]: ! ^ b c
25+
[^a]: ! ^ b c
26+
[!a-b]: ! ^ c
27+
[^a-b]: ! ^ c
28+
[!]a]: ! ^ b c
29+
[^]a]: ! ^ b c
30+
[a^]: ^ a

‎main/php_glob.c‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107

108108
#include "charclass.h"
109109

110+
#define CIRCUMFLEX '^'
110111
#define DOLLAR '$'
111112
#define DOT '.'
112113
#define EOS '\0'
@@ -566,6 +567,12 @@ static int glob0(const Char *pattern, php_glob_t *pglob, struct glob_lim *limitp
566567
switch (c) {
567568
case LBRACKET:
568569
c = *qpatnext;
570+
/*
571+
* Not in OpenBSD's glob(), keep it when syncing: like fnmatch(),
572+
* treat a leading '^' as '!'.
573+
*/
574+
if (c == CIRCUMFLEX)
575+
c = NOT;
569576
if (c == NOT)
570577
++qpatnext;
571578
if (*qpatnext == EOS ||

0 commit comments

Comments
 (0)