Skip to content

Commit 4a51da7

Browse files
authored
Merge pull request #55 from gapple/cleanup
Cleanup
2 parents 21a5cc2 + 3dd9468 commit 4a51da7

14 files changed

+105
-77
lines changed

src/Bytes.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace gapple\StructuredFields;
66

7-
class Bytes
7+
class Bytes implements \Stringable
88
{
99
public function __construct(private readonly string $value)
1010
{

src/Dictionary.php

-3
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ public static function fromArray(array $array): self
3737
}
3838

3939
/**
40-
* @param string $name
4140
* @return TupleInterface|array{mixed, object}|null
4241
*/
4342
public function __get(string $name): mixed
@@ -46,9 +45,7 @@ public function __get(string $name): mixed
4645
}
4746

4847
/**
49-
* @param string $name
5048
* @param TupleInterface|array{mixed, object} $value
51-
* @return void
5249
*/
5350
public function __set(string $name, mixed $value): void
5451
{

src/DisplayString.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace gapple\StructuredFields;
66

7-
class DisplayString
7+
class DisplayString implements \Stringable
88
{
99
public function __construct(private readonly string $value)
1010
{

src/InnerList.php

+1-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ class InnerList implements TupleInterface
1010

1111
/**
1212
* @param array<TupleInterface|array{mixed, object}> $value
13-
* @param object|null $parameters
1413
*/
1514
public function __construct(array $value, ?object $parameters = null)
1615
{
@@ -25,11 +24,10 @@ public function __construct(array $value, ?object $parameters = null)
2524
*
2625
* @param array<mixed> $array
2726
* An array of bare items or TupleInterface objects.
28-
* @return InnerList
2927
*/
3028
public static function fromArray(array $array): InnerList
3129
{
32-
array_walk($array, function (&$item) {
30+
array_walk($array, function (&$item): void {
3331
if (!$item instanceof TupleInterface) {
3432
$item = new Item($item);
3533
} elseif ($item instanceof InnerList) {
@@ -43,7 +41,6 @@ public static function fromArray(array $array): InnerList
4341

4442
/**
4543
* @param TupleInterface|array{mixed, object} $value
46-
* @return void
4744
*/
4845
private static function validateItemType(mixed $value): void
4946
{

src/Item.php

-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ class Item implements TupleInterface
88
{
99
use TupleTrait;
1010

11-
/**
12-
* @param mixed $value
13-
* @param object|null $parameters
14-
*/
1511
public function __construct(mixed $value, ?object $parameters = null)
1612
{
1713
$this->value = $value;

src/OuterList.php

+1-7
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,10 @@ public function __construct(array $value = [])
3131
* Create an OuterList from an array of bare values.
3232
*
3333
* @param array<mixed> $array
34-
* @return OuterList
3534
*/
3635
public static function fromArray(array $array): OuterList
3736
{
38-
array_walk($array, function (&$item) {
37+
array_walk($array, function (&$item): void {
3938
if (!$item instanceof TupleInterface) {
4039
if (is_array($item)) {
4140
$item = InnerList::fromArray($item);
@@ -51,7 +50,6 @@ public static function fromArray(array $array): OuterList
5150

5251
/**
5352
* @param TupleInterface|array{mixed, object} $value
54-
* @return void
5553
*/
5654
private static function validateItemType(mixed $value): void
5755
{
@@ -77,7 +75,6 @@ public function getIterator(): \Iterator
7775

7876
/**
7977
* @param int $offset
80-
* @return bool
8178
*/
8279
public function offsetExists($offset): bool
8380
{
@@ -86,7 +83,6 @@ public function offsetExists($offset): bool
8683

8784
/**
8885
* @param int $offset
89-
* @return mixed
9086
* @phpstan-return TupleInterface|array{mixed, object}|null
9187
*/
9288
public function offsetGet($offset): mixed
@@ -97,7 +93,6 @@ public function offsetGet($offset): mixed
9793
/**
9894
* @param int|null $offset
9995
* @param TupleInterface|array{mixed, object} $value
100-
* @return void
10196
*/
10297
public function offsetSet($offset, $value): void
10398
{
@@ -112,7 +107,6 @@ public function offsetSet($offset, $value): void
112107

113108
/**
114109
* @param int $offset
115-
* @return void
116110
*/
117111
public function offsetUnset($offset): void
118112
{

src/Parameters.php

-9
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,11 @@ public static function fromArray(array $array): self
2626
return $parameters;
2727
}
2828

29-
/**
30-
* @param string $name
31-
* @return mixed|null
32-
*/
3329
public function __get(string $name): mixed
3430
{
3531
return $this->value[$name] ?? null;
3632
}
3733

38-
/**
39-
* @param string $name
40-
* @param mixed $value
41-
* @return void
42-
*/
4334
public function __set(string $name, mixed $value): void
4435
{
4536
$this->value[$name] = $value;

src/Parser.php

+24-23
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ public static function parseDictionary(string $string): Dictionary
2020
while (true) {
2121
$key = self::parseKey($input);
2222

23-
if ($input->isChar('=')) {
24-
$input->consumeChar();
23+
if ($input->skipNextCharIf('=')) {
2524
$value->{$key} = self::parseItemOrInnerList($input);
2625
} else {
2726
// Bare boolean true value.
@@ -85,7 +84,7 @@ public static function parseList(string $string): OuterList
8584

8685
private static function parseItemOrInnerList(ParsingInput $input): TupleInterface
8786
{
88-
if ($input->isChar('(')) {
87+
if ($input->isNextChar('(')) {
8988
return self::parseInnerList($input);
9089
} else {
9190
return self::doParseItem($input);
@@ -104,8 +103,7 @@ private static function parseInnerList(ParsingInput $input): InnerList
104103
while (!$input->empty()) {
105104
$input->trim();
106105

107-
if ($input->isChar(')')) {
108-
$input->consumeChar();
106+
if ($input->skipNextCharIf(')')) {
109107
return new InnerList(
110108
$value,
111109
self::parseParameters($input)
@@ -114,7 +112,7 @@ private static function parseInnerList(ParsingInput $input): InnerList
114112

115113
$value[] = self::doParseItem($input);
116114

117-
if (!($input->isChar(' ') || $input->isChar(')'))) {
115+
if (!($input->isNextChar(' ') || $input->isNextChar(')'))) {
118116
if ($input->empty()) {
119117
break;
120118
}
@@ -126,8 +124,6 @@ private static function parseInnerList(ParsingInput $input): InnerList
126124
}
127125

128126
/**
129-
* @param string $string
130-
*
131127
* @return Item
132128
* A [value, parameters] tuple.
133129
*/
@@ -190,16 +186,15 @@ private static function parseBareItem(ParsingInput $input): mixed
190186
private static function parseParameters(ParsingInput $input): Parameters
191187
{
192188
$parameters = new Parameters();
193-
while ($input->isChar(';')) {
194-
$input->consumeChar();
189+
while ($input->skipNextCharIf(';')) {
195190
$input->trim();
196191

197192
$key = self::parseKey($input);
198-
$parameters->{$key} = true;
199193

200-
if ($input->isChar('=')) {
201-
$input->consumeChar();
194+
if ($input->skipNextCharIf('=')) {
202195
$parameters->{$key} = self::parseBareItem($input);
196+
} else {
197+
$parameters->{$key} = true;
203198
}
204199
}
205200

@@ -275,7 +270,7 @@ private static function parseString(ParsingInput $input): string
275270
}
276271
} elseif ($char === '"') {
277272
return $output;
278-
} elseif (ord($char) <= 0x1f || ord($char) >= 0x7f) {
273+
} elseif (!ctype_print($char)) {
279274
throw new ParseException('Invalid character in string at position ' . ($input->position() - 1));
280275
}
281276

@@ -301,18 +296,21 @@ private static function parseDisplayString(ParsingInput $string): DisplayString
301296
while (!$string->empty()) {
302297
$char = $string->consumeChar();
303298

304-
if (ord($char) <= 0x1f || ord($char) >= 0x7f) {
299+
if (!ctype_print($char)) {
305300
throw new ParseException(
306301
'Invalid character in display string at position ' . ($string->position() - 1)
307302
);
308303
} elseif ($char === '%') {
309-
try {
310-
$encodedString .= '%' . $string->consumeRegex('/^[0-9a-f]{2}/');
311-
} catch (\RuntimeException) {
304+
if ($string->remainingLength() < 2) {
305+
break;
306+
}
307+
$encodedChar = $string->consume(2);
308+
if (!ctype_xdigit($encodedChar) || ctype_upper($encodedChar)) {
312309
throw new ParseException(
313310
'Invalid hex values in display string at position ' . ($string->position() - 1)
314311
);
315312
}
313+
$encodedString .= '%' . $encodedChar;
316314
} elseif ($char === '"') {
317315
$displayString = new DisplayString(rawurldecode($encodedString));
318316
// An invalid UTF-8 subject will cause the preg_* function to match nothing.
@@ -334,16 +332,19 @@ private static function parseDisplayString(ParsingInput $string): DisplayString
334332
*/
335333
private static function parseToken(ParsingInput $input): Token
336334
{
337-
// Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing
338-
// 3.2.6. Field Value Components
339-
// @see https://tools.ietf.org/html/rfc7230#section-3.2.6
340-
$tchar = preg_quote("!#$%&'*+-.^_`|~");
335+
// RFC 9110: HTTP Semantics (5.6.2. Tokens)
336+
// @see https://www.rfc-editor.org/rfc/rfc9110.html#name-tokens
337+
// $tchar = preg_quote("!#$%&'*+-.^_`|~");
338+
$tchar = "!#$%&'*+\-.^_`|~";
341339

342340
// parseToken is only called by parseBareItem if the initial character
343341
// is valid, so a Token object is always returned. If there is an
344342
// invalid character in the token, the public function that was called
345343
// will detect that the remainder of the input string is invalid.
346-
return new Token($input->consumeRegex('/^([a-z*][a-z0-9:\/' . $tchar . ']*)/i'));
344+
return new Token($input->consumeRegex('/^(
345+
(?:\*|[a-z]) # an alphabetic character or "*"
346+
[a-z0-9:\/' . $tchar . ']* # zero to many token characters
347+
)/ix'));
347348
}
348349

349350
/**

src/ParsingInput.php

+24-1
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,17 @@ public function remaining(): string
3333
return substr($this->value, $this->position);
3434
}
3535

36+
public function remainingLength(): int
37+
{
38+
return $this->length - $this->position();
39+
}
40+
3641
/**
3742
* Trim whitespace from beginning of string.
3843
*
3944
* @param bool $ows
4045
* Whether all Optional Whitespace characters should be trimmed. If false, only space characters are trimmed.
4146
* @see https://tools.ietf.org/html/rfc7230#section-3.2.3
42-
* @return void
4347
*/
4448
public function trim(bool $ows = false): void
4549
{
@@ -54,7 +58,16 @@ public function trim(bool $ows = false): void
5458
}
5559
}
5660

61+
/**
62+
* @deprecated in 2.3.0 and will be removed in 3.0.0
63+
* @codeCoverageIgnore
64+
*/
5765
public function isChar(string $char): bool
66+
{
67+
return $this->isNextChar($char);
68+
}
69+
70+
public function isNextChar(string $char): bool
5871
{
5972
assert(strlen($char) === 1);
6073

@@ -70,6 +83,15 @@ public function getChar(): string
7083
return $this->value[$this->position];
7184
}
7285

86+
public function skipNextCharIf(string $char): bool
87+
{
88+
if ($this->isNextChar($char)) {
89+
$this->position++;
90+
return true;
91+
}
92+
return false;
93+
}
94+
7395
/**
7496
* @phpstan-impure
7597
*/
@@ -114,6 +136,7 @@ public function consumeString(string $value): void
114136
public function consumeRegex(string $pattern): string
115137
{
116138
assert(str_starts_with($pattern, '/^'));
139+
assert(!preg_match('/\$\/[a-z]+$/i', $pattern));
117140

118141
if (preg_match($pattern, $this->remaining(), $matches)) {
119142
$this->position += strlen($matches[0]);

src/Serializer.php

+15-15
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,10 @@ class Serializer
99
/**
1010
* Serialize an item with optional parameters.
1111
*
12-
* @param mixed $value
12+
* @param Item|mixed $value
1313
* A bare value, or an Item object.
1414
* @param object|null $parameters
15-
* An optional object containing parameter values if a bare value is provided.
16-
*
17-
* @return string
18-
* The serialized value.
15+
* If a bare value is provided, an optional object containing parameter values.
1916
*/
2017
public static function serializeItem(mixed $value, ?object $parameters = null): string
2118
{
@@ -43,7 +40,6 @@ public static function serializeItem(mixed $value, ?object $parameters = null):
4340

4441
/**
4542
* @param iterable<TupleInterface|array{mixed, object}> $value
46-
* @return string
4743
*/
4844
public static function serializeList(iterable $value): string
4945
{
@@ -205,11 +201,10 @@ private static function serializeDecimal(float $value): string
205201

206202
private static function serializeString(string $value): string
207203
{
208-
if (preg_match('/[^\x20-\x7E]/i', $value)) {
204+
if (!empty($value) && !ctype_print($value)) {
209205
throw new SerializeException("Invalid characters in string");
210206
}
211-
212-
return '"' . preg_replace('/(["\\\])/', '\\\$1', $value) . '"';
207+
return '"' . str_replace(['\\', '"'], ['\\\\', '\"'], $value) . '"';
213208
}
214209

215210
private static function serializeDisplayString(DisplayString $value): string
@@ -224,12 +219,17 @@ private static function serializeDisplayString(DisplayString $value): string
224219

225220
private static function serializeToken(Token $value): string
226221
{
227-
// Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing
228-
// 3.2.6. Field Value Components
229-
// @see https://tools.ietf.org/html/rfc7230#section-3.2.6
230-
$tchar = preg_quote("!#$%&'*+-.^_`|~");
231-
232-
if (!preg_match('/^((?:\*|[a-z])[a-z0-9:\/' . $tchar . ']*)$/i', (string) $value)) {
222+
// RFC 9110: HTTP Semantics (5.6.2. Tokens)
223+
// @see https://www.rfc-editor.org/rfc/rfc9110.html#name-tokens
224+
// $tchar = preg_quote("!#$%&'*+-.^_`|~");
225+
$tchar = "!#$%&'*+\-.^_`|~";
226+
227+
if (
228+
!preg_match('/^(
229+
(?:\*|[a-z]) # an alphabetic character or "*"
230+
[a-z0-9:\/' . $tchar . ']* # zero to many token characters
231+
)$/ix', (string) $value)
232+
) {
233233
throw new SerializeException('Invalid characters in token');
234234
}
235235

0 commit comments

Comments
 (0)