@@ -15,29 +15,110 @@ final class PhpReader implements Reader
15
15
16
16
/**
17
17
* @throws InvalidSession if the session data cannot be deserialized
18
- *
19
- * @see https://www.php.net/manual/en/function.session-decode.php#108037
20
18
*/
21
19
public function read (string $ data ): array
22
20
{
23
21
$ deserialized = [];
24
22
$ offset = 0 ;
25
23
26
24
while ($ offset < \strlen ($ data )) {
27
- if (!str_contains (substr ($ data , $ offset ), self ::DELIMITER )) {
25
+ $ currentPos = strpos ($ data , self ::DELIMITER , $ offset );
26
+
27
+ if (false === $ currentPos ) {
28
28
throw new InvalidSession ($ data , 'Cannot deserialize session data. ' );
29
29
}
30
30
31
- $ pos = strpos ($ data , self ::DELIMITER , $ offset );
32
- $ num = $ pos - $ offset ;
33
- $ variable = substr ($ data , $ offset , $ num );
34
- $ offset += $ num + 1 ;
35
- $ deserializedSection = unserialize (substr ($ data , $ offset ));
31
+ $ name = substr ($ data , $ offset , $ currentPos - $ offset );
32
+ $ offset = $ currentPos + 1 ;
33
+
34
+ // Find the position for the end of the serialized data so we can correctly chop the next variable if need be
35
+ $ serializedLength = $ this ->getSerializedSegmentLength (substr ($ data , $ offset ));
36
+
37
+ if (false === $ serializedLength ) {
38
+ throw new InvalidSession ($ data , 'Cannot deserialize session data. ' );
39
+ }
40
+
41
+ $ rawData = substr ($ data , $ offset , $ serializedLength );
42
+
43
+ $ value = unserialize ($ rawData );
36
44
37
- $ deserialized [$ variable ] = $ deserializedSection ;
38
- $ offset += \strlen (serialize ($ deserializedSection ));
45
+ $ deserialized [$ name ] = $ value ;
46
+
47
+ $ offset += $ serializedLength ;
39
48
}
40
49
41
50
return $ deserialized ;
42
51
}
52
+
53
+ private function getSerializedSegmentLength (string $ data ): int |false
54
+ {
55
+ // No serialized value can have a length of less than 4 characters
56
+ if (\strlen ($ data ) < 4 ) {
57
+ return false ;
58
+ }
59
+
60
+ // The data type will be in position 0
61
+ switch ($ data [0 ]) {
62
+ // Null value
63
+ case 'N ' :
64
+ return 2 ;
65
+
66
+ // Boolean value
67
+ case 'b ' :
68
+ return 4 ;
69
+
70
+ // Integer or floating point value
71
+ case 'i ' :
72
+ case 'd ' :
73
+ $ end = strpos ($ data , '; ' );
74
+
75
+ return false === $ end ? false : $ end + 1 ;
76
+
77
+ // String value
78
+ case 's ' :
79
+ if (!preg_match ('/^s:\d+:"/ ' , $ data , $ matches )) {
80
+ return false ;
81
+ }
82
+
83
+ // Add characters for the closing quote and semicolon
84
+ return \strlen ($ matches [0 ]) + (int ) substr ($ matches [0 ], 2 , -2 ) + 2 ;
85
+
86
+ // Array value
87
+ case 'a ' :
88
+ if (!preg_match ('/^a:\d+:\{/ ' , $ data , $ matches )) {
89
+ return false ;
90
+ }
91
+
92
+ $ start = \strlen ($ matches [0 ]);
93
+ $ count = (int ) substr ($ matches [0 ], 2 , -2 );
94
+ $ offset = $ start ;
95
+ $ length = \strlen ($ data );
96
+
97
+ // Double the count to account for each element having a key and value
98
+ for ($ i = 0 ; $ i < $ count * 2 ; ++$ i ) {
99
+ $ segmentLength = $ this ->getSerializedSegmentLength (substr ($ data , $ offset ));
100
+
101
+ if (false === $ segmentLength ) {
102
+ return false ;
103
+ }
104
+
105
+ $ offset += $ segmentLength ;
106
+
107
+ if ($ offset >= $ length ) {
108
+ return false ;
109
+ }
110
+ }
111
+
112
+ if ('} ' !== $ data [$ offset ]) {
113
+ return false ;
114
+ }
115
+
116
+ // Add characters for the closing brace
117
+ return $ offset + 1 ;
118
+
119
+ // Unsupported value
120
+ default :
121
+ return false ;
122
+ }
123
+ }
43
124
}
0 commit comments