9
9
use function in_array ;
10
10
use function ltrim ;
11
11
use function preg_match ;
12
+ use function str_contains ;
12
13
use function strlen ;
13
14
use function substr ;
14
15
28
29
*/
29
30
final class Parser
30
31
{
32
+ private const REGEXP_BYTE_SEQUENCE = '/^(?<sequence>:(?<byte>[a-z\d+\/=]*):)/i ' ;
33
+ private const REGEXP_BOOLEAN = '/^\?[01]/ ' ;
34
+ private const REGEXP_DATE = '/^@(?<date>-?\d{1,15})(?:[^\d.]|$)/ ' ;
35
+ private const REGEXP_DECIMAL = '/^-?\d{1,12}\.\d{1,3}$/ ' ;
36
+ private const REGEXP_INTEGER = '/^-?\d{1,15}$/ ' ;
37
+ private const REGEXP_TOKEN = "/^(?<token>[a-z*][a-z\d:\/!# \$%&'*+\-.^_`|~]*)/i " ;
38
+ private const REGEXP_INVALID_CHARACTERS = "/[ \r\t\n]|[^ \x20- \x7E]/ " ;
39
+ private const REGEXP_VALID_NUMBER = '/^(?<number>-?\d+(?:\.\d+)?)(?:[^\d.]|$)/ ' ;
40
+ private const REGEXP_VALID_SPACE = '/^(?<space>,[ \t]*)/ ' ;
41
+ private const FIRST_CHARACTER_RANGE_NUMBER = '-1234567890 ' ;
42
+ private const FIRST_CHARACTER_RANGE_TOKEN = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ* ' ;
43
+
44
+ /**
45
+ * @return array{0:SfType, 1:array<string, SfType>}
46
+ */
47
+ public static function parseItem (Stringable |string $ httpValue ): array
48
+ {
49
+ $ itemString = trim ((string ) $ httpValue , ' ' );
50
+ if ('' === $ itemString || 1 === preg_match (self ::REGEXP_INVALID_CHARACTERS , $ itemString )) {
51
+ throw new SyntaxError ('The HTTP textual representation " ' .$ httpValue .'" for an item contains invalid characters. ' );
52
+ }
53
+
54
+ [$ value , $ offset ] = Parser::parseBareItem ($ itemString );
55
+ $ remainder = substr ($ itemString , $ offset );
56
+ if ('' !== $ remainder && !str_contains ($ remainder , '; ' )) {
57
+ throw new SyntaxError ('The HTTP textual representation " ' .$ httpValue .'" for an item contains invalid characters. ' );
58
+ }
59
+
60
+ return [$ value , self ::parseParameters ($ remainder )];
61
+ }
62
+
63
+ /**
64
+ * Returns an instance from an HTTP textual representation.
65
+ *
66
+ * @see https://www.rfc-editor.org/rfc/rfc8941.html#section-3.1.2
67
+ *
68
+ * @throws SyntaxError If the string is not a valid
69
+ *
70
+ * @return array<string, SfType>
71
+ */
72
+ public static function parseParameters (Stringable |string $ httpValue ): array
73
+ {
74
+ $ httpValue = trim ((string ) $ httpValue );
75
+ [$ parameters , $ offset ] = Parser::parseContainedParameters ($ httpValue );
76
+ if (strlen ($ httpValue ) !== $ offset ) {
77
+ throw new SyntaxError ('The HTTP textual representation " ' .$ httpValue .'" for Parameters contains invalid characters. ' );
78
+ }
79
+
80
+ return $ parameters ;
81
+ }
82
+
31
83
/**
32
84
* Returns an ordered list represented as a PHP list array from an HTTP textual representation.
33
85
*
@@ -107,7 +159,7 @@ private static function removeCommaSeparatedWhiteSpaces(string $httpValue, int $
107
159
return $ httpValue ;
108
160
}
109
161
110
- if (1 !== preg_match (' /^(?<space>,[ \t]*)/ ' , $ httpValue , $ found )) {
162
+ if (1 !== preg_match (self :: REGEXP_VALID_SPACE , $ httpValue , $ found )) {
111
163
throw new SyntaxError ('The HTTP textual representation is missing an excepted comma. ' );
112
164
}
113
165
@@ -130,7 +182,7 @@ private static function removeOptionalWhiteSpaces(string $httpValue): string
130
182
}
131
183
132
184
/**
133
- * Returns an Item value object or an inner list as a PHP list array from an HTTP textual representation.
185
+ * Returns an item or an inner list as a PHP list array from an HTTP textual representation.
134
186
*
135
187
* @see https://www.rfc-editor.org/rfc/rfc8941.html#section-4.2.1.1
136
188
*
@@ -142,7 +194,7 @@ private static function parseItemOrInnerList(string $httpValue): array
142
194
return self ::parseInnerListValue ($ httpValue );
143
195
}
144
196
145
- [$ item , $ remainder ] = self ::parseItem ($ httpValue );
197
+ [$ item , $ remainder ] = self ::parseContainedItem ($ httpValue );
146
198
147
199
return [$ item , strlen ($ httpValue ) - strlen ($ remainder )];
148
200
}
@@ -163,13 +215,13 @@ private static function parseInnerListValue(string $httpValue): array
163
215
164
216
if (') ' === $ remainder [0 ]) {
165
217
$ remainder = substr ($ remainder , 1 );
166
- [$ parameters , $ offset ] = self ::parseParameters ($ remainder );
218
+ [$ parameters , $ offset ] = self ::parseContainedParameters ($ remainder );
167
219
$ remainder = substr ($ remainder , $ offset );
168
220
169
221
return [[$ list , $ parameters ], strlen ($ httpValue ) - strlen ($ remainder )];
170
222
}
171
223
172
- [$ list [], $ remainder ] = self ::parseItem ($ remainder );
224
+ [$ list [], $ remainder ] = self ::parseContainedItem ($ remainder );
173
225
174
226
if ('' !== $ remainder && !in_array ($ remainder [0 ], [' ' , ') ' ], true )) {
175
227
throw new SyntaxError ("The HTTP textual representation \"$ remainder \" for a inner list is using invalid characters. " );
@@ -180,33 +232,35 @@ private static function parseInnerListValue(string $httpValue): array
180
232
}
181
233
182
234
/**
235
+ * Returns an item represented as a PHP array from an HTTP textual representation and the consumed offset in a tuple.
236
+ *
183
237
* @return array{0:array{0:SfType, 1:array<string, SfType>}, 1:string}
184
238
*/
185
- private static function parseItem (string $ remainder ): array
239
+ private static function parseContainedItem (string $ remainder ): array
186
240
{
187
241
[$ value , $ offset ] = self ::parseBareItem ($ remainder );
188
242
$ remainder = substr ($ remainder , $ offset );
189
- [$ parameters , $ offset ] = self ::parseParameters ($ remainder );
243
+ [$ parameters , $ offset ] = self ::parseContainedParameters ($ remainder );
190
244
191
245
return [[$ value , $ parameters ], substr ($ remainder , $ offset )];
192
246
}
193
247
194
248
/**
195
- * Returns an Item value from an HTTP textual representation and the consumed offset in a tuple.
249
+ * Returns an item value from an HTTP textual representation and the consumed offset in a tuple.
196
250
*
197
251
* @see https://www.rfc-editor.org/rfc/rfc8941.html#section-4.2.3.1
198
252
*
199
253
* @return array{0:SfType, 1:int}
200
254
*/
201
- public static function parseBareItem (string $ httpValue ): array
255
+ private static function parseBareItem (string $ httpValue ): array
202
256
{
203
257
return match (true ) {
204
258
'" ' === $ httpValue [0 ] => self ::parseString ($ httpValue ),
205
259
': ' === $ httpValue [0 ] => self ::parseByteSequence ($ httpValue ),
206
260
'? ' === $ httpValue [0 ] => self ::parseBoolean ($ httpValue ),
207
261
'@ ' === $ httpValue [0 ] => self ::parseDate ($ httpValue ),
208
- 1 === preg_match ( ' /^(-|\d)/ ' , $ httpValue ) => self ::parseNumber ($ httpValue ),
209
- 1 === preg_match ( ' /^([a-z*])/i ' , $ httpValue ) => self ::parseToken ($ httpValue ),
262
+ str_contains ( self :: FIRST_CHARACTER_RANGE_NUMBER , $ httpValue[ 0 ] ) => self ::parseNumber ($ httpValue ),
263
+ str_contains ( self :: FIRST_CHARACTER_RANGE_TOKEN , $ httpValue[ 0 ] ) => self ::parseToken ($ httpValue ),
210
264
default => throw new SyntaxError ("The HTTP textual representation \"$ httpValue \" for an Item is unknown or unsupported. " ),
211
265
};
212
266
}
@@ -218,9 +272,10 @@ public static function parseBareItem(string $httpValue): array
218
272
*
219
273
* @return array{0:array<string, SfType>, 1:int}
220
274
*/
221
- public static function parseParameters ( string $ httpValue ): array
275
+ private static function parseContainedParameters ( Stringable | string $ httpValue ): array
222
276
{
223
277
$ map = [];
278
+ $ httpValue = (string ) $ httpValue ;
224
279
$ remainder = $ httpValue ;
225
280
while ('' !== $ remainder && '; ' === $ remainder [0 ]) {
226
281
$ remainder = ltrim (substr ($ remainder , 1 ), ' ' );
@@ -249,7 +304,7 @@ public static function parseParameters(string $httpValue): array
249
304
*/
250
305
private static function parseBoolean (string $ httpValue ): array
251
306
{
252
- if (1 !== preg_match (' /^\?[01]/ ' , $ httpValue )) {
307
+ if (1 !== preg_match (self :: REGEXP_BOOLEAN , $ httpValue )) {
253
308
throw new SyntaxError ("The HTTP textual representation \"$ httpValue \" for a Boolean contains invalid characters. " );
254
309
}
255
310
@@ -265,13 +320,13 @@ private static function parseBoolean(string $httpValue): array
265
320
*/
266
321
private static function parseNumber (string $ httpValue ): array
267
322
{
268
- if (1 !== preg_match (' /^(?<number>-?\d+(?:\.\d+)?)(?:[^\d.]|$)/ ' , $ httpValue , $ found )) {
323
+ if (1 !== preg_match (self :: REGEXP_VALID_NUMBER , $ httpValue , $ found )) {
269
324
throw new SyntaxError ("The HTTP textual representation \"$ httpValue \" for a Number contains invalid characters. " );
270
325
}
271
326
272
327
return match (true ) {
273
- 1 === preg_match (' /^-?\d{1,12}\.\d{1,3}$/ ' , $ found ['number ' ]) => [(float ) $ found ['number ' ], strlen ($ found ['number ' ])],
274
- 1 === preg_match (' /^-?\d{1,15}$/ ' , $ found ['number ' ]) => [(int ) $ found ['number ' ], strlen ($ found ['number ' ])],
328
+ 1 === preg_match (self :: REGEXP_DECIMAL , $ found ['number ' ]) => [(float ) $ found ['number ' ], strlen ($ found ['number ' ])],
329
+ 1 === preg_match (self :: REGEXP_INTEGER , $ found ['number ' ]) => [(int ) $ found ['number ' ], strlen ($ found ['number ' ])],
275
330
default => throw new SyntaxError ("The HTTP textual representation \"$ httpValue \" for a Number contains too much digit. " ),
276
331
};
277
332
}
@@ -285,7 +340,7 @@ private static function parseNumber(string $httpValue): array
285
340
*/
286
341
private static function parseDate (string $ httpValue ): array
287
342
{
288
- if (1 !== preg_match (' /^@(?<date>-?\d{1,15})(?:[^\d.]|$)/ ' , $ httpValue , $ found )) {
343
+ if (1 !== preg_match (self :: REGEXP_DATE , $ httpValue , $ found )) {
289
344
throw new SyntaxError ("The HTTP textual representation \"$ httpValue \" for a Date contains invalid characters. " );
290
345
}
291
346
@@ -314,7 +369,7 @@ private static function parseString(string $httpValue): array
314
369
return [$ output , $ offset ];
315
370
}
316
371
317
- if (1 === preg_match (" /[^ \x20 - \x7E ]/ " , $ char )) {
372
+ if (1 === preg_match (self :: REGEXP_INVALID_CHARACTERS , $ char )) {
318
373
throw new SyntaxError ("The HTTP textual representation \"$ originalHttpValue \" for a String contains an invalid end string. " );
319
374
}
320
375
@@ -348,7 +403,7 @@ private static function parseString(string $httpValue): array
348
403
*/
349
404
private static function parseToken (string $ httpValue ): array
350
405
{
351
- preg_match (" /^(?<token>[a-z*][a-z\d:\/!# \$ %&'*+\-.^_`|~]*)/i " , $ httpValue , $ found );
406
+ preg_match (self :: REGEXP_TOKEN , $ httpValue , $ found );
352
407
353
408
return [Token::fromString ($ found ['token ' ]), strlen ($ found ['token ' ])];
354
409
}
@@ -362,7 +417,7 @@ private static function parseToken(string $httpValue): array
362
417
*/
363
418
private static function parseByteSequence (string $ httpValue ): array
364
419
{
365
- if (1 !== preg_match (' /^(?<sequence>:(?<byte>[a-z\d+\/=]*):)/i ' , $ httpValue , $ matches )) {
420
+ if (1 !== preg_match (self :: REGEXP_BYTE_SEQUENCE , $ httpValue , $ matches )) {
366
421
throw new SyntaxError ("The HTTP textual representation \"$ httpValue \" for a Byte Sequence contains invalid characters. " );
367
422
}
368
423
0 commit comments