Skip to content

Commit 0257ea5

Browse files
committed
:octocat: StreamUtil: stream mode checks instead of constants
1 parent c7861ba commit 0257ea5

File tree

1 file changed

+80
-4
lines changed

1 file changed

+80
-4
lines changed

src/StreamUtil.php

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,92 @@
1010

1111
namespace chillerlan\HTTP\Utils;
1212

13+
use InvalidArgumentException;
1314
use Psr\Http\Message\StreamInterface;
15+
use function in_array;
16+
use function preg_match;
17+
use function str_contains;
18+
use function substr;
1419

1520
/**
1621
*
1722
*/
18-
class StreamUtil{
23+
final class StreamUtil{
1924

20-
public const MODES_READ_WRITE = ['a+', 'c+', 'c+b', 'c+t', 'r+' , 'r+b', 'r+t', 'w+' , 'w+b', 'w+t', 'x+' , 'x+b', 'x+t'];
21-
public const MODES_READ = [...self::MODES_READ_WRITE, 'r', 'rb', 'rt'];
22-
public const MODES_WRITE = [...self::MODES_READ_WRITE, 'a', 'rw', 'w', 'wb'];
25+
/**
26+
* Checks whether the given mode allows reading and writing
27+
*/
28+
public static function modeAllowsReadWrite(string $mode):bool{
29+
return str_contains(self::validateMode($mode), '+');
30+
}
31+
32+
/**
33+
* Checks whether the given mode allows only reading
34+
*/
35+
public static function modeAllowsReadOnly(string $mode):bool{
36+
$mode = self::validateMode($mode);
37+
38+
return $mode[0] === 'r' && !str_contains($mode, '+');
39+
}
40+
41+
/**
42+
* Checks whether the given mode allows only writing
43+
*/
44+
public static function modeAllowsWriteOnly(string $mode):bool{
45+
$mode = self::validateMode($mode);
46+
47+
return in_array($mode[0], ['a', 'c', 'w', 'x']) && !str_contains($mode, '+');
48+
}
49+
50+
/**
51+
* Checks whether the given mode allows reading
52+
*/
53+
public static function modeAllowsRead(string $mode):bool{
54+
$mode = self::validateMode($mode);
55+
56+
return $mode[0] === 'r' || (in_array($mode[0], ['a', 'c', 'w', 'x']) && str_contains($mode, '+'));
57+
}
58+
59+
/**
60+
* Checks whether the given mode allows writing
61+
*/
62+
public static function modeAllowsWrite(string $mode):bool{
63+
$mode = self::validateMode($mode);
64+
65+
return in_array($mode[0], ['a', 'c', 'w', 'x']) || ($mode[0] === 'r' && str_contains($mode, '+'));
66+
}
67+
68+
/**
69+
* Checks if the given mode is valid for fopen().
70+
* Returns the first 15 characters, throws if that string doesn't match the pattern.
71+
*
72+
* Note: we don't care where the modifier flags are in the string, what matters is that the first character
73+
* is one of "acrwx" and the rest may contain one of "bet+" from 2nd position onwards, so "aaaaaaaaaaaaaa+b" is valid.
74+
*
75+
* The documentation of fopen() says that the text-mode translation flag (b/t) should be added as last character,
76+
* however, it doesn't matter as PHP internally only reads the mode from the first character and 15 characters total.
77+
* and does a strchr() on it for the flags, so technically "rb+" is equivalent to "r+b" and "rrrbbbb++".
78+
* Also, some libraries allow a mode "rw" which is wrong and just falls back to "r" - see above. (looking at you, Guzzle)
79+
*
80+
* gzopen() adds a bunch of other flags that are hardly documented, so we'll ignore these until we get a full list.
81+
*
82+
* @see https://www.php.net/manual/en/function.fopen
83+
* @see https://www.php.net/manual/en/function.gzopen.php
84+
* @see https://stackoverflow.com/a/44483367/3185624
85+
* @see https://github.com/php/php-src/blob/6602ddead5c81fb67ebf2b21c32b58aa1de67699/main/streams/plain_wrapper.c#L71-L121
86+
* @see https://github.com/guzzle/psr7/blob/815698d9f11c908bc59471d11f642264b533346a/src/Stream.php#L19
87+
*
88+
* @throws \InvalidArgumentException
89+
*/
90+
public static function validateMode(string $mode):string{
91+
$mode = substr($mode, 0, 15);
92+
93+
if(!preg_match('/^[acrwx]+[befht+\d]*$/', $mode)){ // [bet+]*
94+
throw new InvalidArgumentException('invalid fopen mode: '.$mode);
95+
}
96+
97+
return $mode;
98+
}
2399

24100
/**
25101
* Reads the content from a stream and make sure we rewind

0 commit comments

Comments
 (0)