PluginProbe
DoLogin Security / trunk
DoLogin Security vtrunk
5.0.10 4.8.3 trunk 1.0 1.1 1.1.1 1.2 1.2.1 1.2.2 1.3 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.5 1.6 All 64 releases
dologin / src / klsso-msgpack.cls.php

klsso-msgpack.cls.php in DoLogin Security trunk, at src/klsso-msgpack.cls.php

330 lines 9.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Bounded MessagePack codec for KeyLockr SSO.
4 *
5 * @since 4.6.5
6 * @package dologin
7 */
8
9 namespace dologin;
10
11 defined( 'WPINC' ) || exit;
12
13 class KLSso_MsgPack_Bin {
14 public $bytes;
15
16 public function __construct( $bytes ) {
17 $this->bytes = (string) $bytes;
18 }
19 }
20
21 class KLSso_MsgPack {
22 const MAX_INPUT_BYTES = 262144;
23 const MAX_ITEMS = 4096;
24 const MAX_DEPTH = 32;
25
26 public static function bin( $bytes ) {
27 return new KLSso_MsgPack_Bin( $bytes );
28 }
29
30 public static function pack( $value ) {
31 if ( $value instanceof KLSso_MsgPack_Bin ) {
32 return self::pack_bin( $value->bytes );
33 }
34 if ( is_null( $value ) ) {
35 return "\xc0";
36 }
37 if ( is_bool( $value ) ) {
38 return $value ? "\xc3" : "\xc2";
39 }
40 if ( is_int( $value ) ) {
41 return self::pack_int( $value );
42 }
43 if ( is_float( $value ) ) {
44 return "\xcb" . self::pack_float_be( $value, 'd' );
45 }
46 if ( is_string( $value ) ) {
47 return self::pack_str( $value );
48 }
49 if ( is_array( $value ) ) {
50 return self::is_list( $value ) ? self::pack_array( $value ) : self::pack_map( $value );
51 }
52 return self::pack_str( (string) $value );
53 }
54
55 public static function unpack( $bytes ) {
56 $bytes = (string) $bytes;
57 if ( '' === $bytes ) {
58 throw new \Exception( 'Empty msgpack input' );
59 }
60 if ( strlen( $bytes ) > self::MAX_INPUT_BYTES ) {
61 throw new \Exception( 'Msgpack input exceeds size limit' );
62 }
63 $offset = 0;
64 $items = 0;
65 $value = self::read( $bytes, $offset, 0, $items );
66 if ( $offset !== strlen( $bytes ) ) {
67 throw new \Exception( 'Trailing msgpack data' );
68 }
69 return $value;
70 }
71
72 private static function pack_int( $num ) {
73 if ( $num >= 0 ) {
74 if ( $num < 0x80 ) {
75 return chr( $num );
76 }
77 if ( $num <= 0xff ) {
78 return "\xcc" . pack( 'C', $num );
79 }
80 if ( $num <= 0xffff ) {
81 return "\xcd" . pack( 'n', $num );
82 }
83 if ( $num <= 0xffffffff ) {
84 return "\xce" . pack( 'N', $num );
85 }
86 } elseif ( $num >= -32 ) {
87 return chr( 0xe0 | ( $num + 32 ) );
88 } elseif ( $num >= -128 ) {
89 return "\xd0" . pack( 'c', $num );
90 } elseif ( $num >= -32768 ) {
91 return "\xd1" . pack( 'n', $num & 0xffff );
92 } elseif ( $num >= -2147483648 ) {
93 return "\xd2" . pack( 'N', $num & 0xffffffff );
94 }
95
96 $hi = (int) floor( $num / 4294967296 );
97 $lo = (int) ( $num & 0xffffffff );
98 return ( $num >= 0 ? "\xcf" : "\xd3" ) . pack( 'NN', $hi, $lo );
99 }
100
101 private static function pack_str( $str ) {
102 $len = strlen( $str );
103 if ( $len < 32 ) {
104 return chr( 0xa0 | $len ) . $str;
105 }
106 if ( $len <= 0xff ) {
107 return "\xd9" . pack( 'C', $len ) . $str;
108 }
109 if ( $len <= 0xffff ) {
110 return "\xda" . pack( 'n', $len ) . $str;
111 }
112 return "\xdb" . pack( 'N', $len ) . $str;
113 }
114
115 private static function pack_bin( $str ) {
116 $len = strlen( $str );
117 if ( $len <= 0xff ) {
118 return "\xc4" . pack( 'C', $len ) . $str;
119 }
120 if ( $len <= 0xffff ) {
121 return "\xc5" . pack( 'n', $len ) . $str;
122 }
123 return "\xc6" . pack( 'N', $len ) . $str;
124 }
125
126 private static function pack_array( $array ) {
127 $len = count( $array );
128 $out = $len < 16 ? chr( 0x90 | $len ) : ( $len <= 0xffff ? "\xdc" . pack( 'n', $len ) : "\xdd" . pack( 'N', $len ) );
129 foreach ( $array as $item ) {
130 $out .= self::pack( $item );
131 }
132 return $out;
133 }
134
135 private static function pack_map( $map ) {
136 ksort( $map, SORT_STRING );
137 $len = count( $map );
138 $out = $len < 16 ? chr( 0x80 | $len ) : ( $len <= 0xffff ? "\xde" . pack( 'n', $len ) : "\xdf" . pack( 'N', $len ) );
139 foreach ( $map as $key => $val ) {
140 $out .= self::pack_str( (string) $key ) . self::pack( $val );
141 }
142 return $out;
143 }
144
145 private static function read( $bytes, &$offset, $depth, &$items ) {
146 if ( $depth > self::MAX_DEPTH ) {
147 throw new \Exception( 'Msgpack nesting limit exceeded' );
148 }
149 if ( $offset >= strlen( $bytes ) ) {
150 throw new \Exception( 'Truncated msgpack input' );
151 }
152 $items++;
153 if ( $items > self::MAX_ITEMS ) {
154 throw new \Exception( 'Msgpack item limit exceeded' );
155 }
156 $prefix = ord( $bytes[ $offset++ ] );
157 if ( $prefix <= 0x7f ) {
158 return $prefix;
159 }
160 if ( $prefix >= 0xe0 ) {
161 return $prefix - 0x100;
162 }
163 if ( ( $prefix & 0xe0 ) === 0xa0 ) {
164 return self::read_bytes( $bytes, $offset, $prefix & 0x1f );
165 }
166 if ( ( $prefix & 0xf0 ) === 0x90 ) {
167 return self::read_array( $bytes, $offset, $prefix & 0x0f, $depth, $items );
168 }
169 if ( ( $prefix & 0xf0 ) === 0x80 ) {
170 return self::read_map( $bytes, $offset, $prefix & 0x0f, $depth, $items );
171 }
172
173 switch ( $prefix ) {
174 case 0xc0:
175 return null;
176 case 0xc2:
177 return false;
178 case 0xc3:
179 return true;
180 case 0xc4:
181 return new KLSso_MsgPack_Bin( self::read_bytes( $bytes, $offset, self::read_uint( $bytes, $offset, 1 ) ) );
182 case 0xc5:
183 return new KLSso_MsgPack_Bin( self::read_bytes( $bytes, $offset, self::read_uint( $bytes, $offset, 2 ) ) );
184 case 0xc6:
185 return new KLSso_MsgPack_Bin( self::read_bytes( $bytes, $offset, self::read_uint( $bytes, $offset, 4 ) ) );
186 case 0xca:
187 return self::unpack_float_be( self::read_bytes( $bytes, $offset, 4 ), 'f' );
188 case 0xcb:
189 return self::unpack_float_be( self::read_bytes( $bytes, $offset, 8 ), 'd' );
190 case 0xcc:
191 return self::read_uint( $bytes, $offset, 1 );
192 case 0xcd:
193 return self::read_uint( $bytes, $offset, 2 );
194 case 0xce:
195 return self::read_uint( $bytes, $offset, 4 );
196 case 0xcf:
197 return self::read_uint64( $bytes, $offset );
198 case 0xd0:
199 $val = unpack( 'c', self::read_bytes( $bytes, $offset, 1 ) );
200 return $val[1];
201 case 0xd1:
202 $val = self::read_uint( $bytes, $offset, 2 );
203 return $val & 0x8000 ? $val - 0x10000 : $val;
204 case 0xd2:
205 $val = self::read_uint( $bytes, $offset, 4 );
206 return $val & 0x80000000 ? $val - 0x100000000 : $val;
207 case 0xd3:
208 return self::read_int64( $bytes, $offset );
209 case 0xd9:
210 return self::read_bytes( $bytes, $offset, self::read_uint( $bytes, $offset, 1 ) );
211 case 0xda:
212 return self::read_bytes( $bytes, $offset, self::read_uint( $bytes, $offset, 2 ) );
213 case 0xdb:
214 return self::read_bytes( $bytes, $offset, self::read_uint( $bytes, $offset, 4 ) );
215 case 0xdc:
216 return self::read_array( $bytes, $offset, self::read_uint( $bytes, $offset, 2 ), $depth, $items );
217 case 0xdd:
218 return self::read_array( $bytes, $offset, self::read_uint( $bytes, $offset, 4 ), $depth, $items );
219 case 0xde:
220 return self::read_map( $bytes, $offset, self::read_uint( $bytes, $offset, 2 ), $depth, $items );
221 case 0xdf:
222 return self::read_map( $bytes, $offset, self::read_uint( $bytes, $offset, 4 ), $depth, $items );
223 }
224
225 throw new \Exception( 'Unsupported msgpack type: ' . dechex( $prefix ) );
226 }
227
228 private static function read_bytes( $bytes, &$offset, $len ) {
229 $len = (int) $len;
230 if ( $len < 0 || $offset < 0 || $len > strlen( $bytes ) - $offset ) {
231 throw new \Exception( 'Truncated msgpack input' );
232 }
233 $out = substr( $bytes, $offset, $len );
234 $offset += $len;
235 return $out;
236 }
237
238 private static function read_uint( $bytes, &$offset, $len ) {
239 $data = self::read_bytes( $bytes, $offset, $len );
240 if ( 1 === $len ) {
241 $val = unpack( 'C', $data );
242 } elseif ( 2 === $len ) {
243 $val = unpack( 'n', $data );
244 } else {
245 $val = unpack( 'N', $data );
246 }
247 return $val[1];
248 }
249
250 private static function read_uint64( $bytes, &$offset ) {
251 $parts = unpack( 'Nhi/Nlo', self::read_bytes( $bytes, $offset, 8 ) );
252 return $parts['hi'] * 4294967296 + $parts['lo'];
253 }
254
255 private static function read_int64( $bytes, &$offset ) {
256 $parts = unpack( 'Nhi/Nlo', self::read_bytes( $bytes, $offset, 8 ) );
257 if ( $parts['hi'] & 0x80000000 ) {
258 $hi = ( ~ $parts['hi'] ) & 0xffffffff;
259 $lo = ( ~ $parts['lo'] ) & 0xffffffff;
260 return -1 * ( $hi * 4294967296 + $lo + 1 );
261 }
262 return $parts['hi'] * 4294967296 + $parts['lo'];
263 }
264
265 private static function read_array( $bytes, &$offset, $len, $depth, &$items ) {
266 $len = (int) $len;
267 if ( $len < 0 || $len > self::MAX_ITEMS - $items || $len > strlen( $bytes ) - $offset ) {
268 throw new \Exception( 'Invalid msgpack array length' );
269 }
270 $out = array();
271 for ( $i = 0; $i < $len; $i++ ) {
272 $out[] = self::read( $bytes, $offset, $depth + 1, $items );
273 }
274 return $out;
275 }
276
277 private static function read_map( $bytes, &$offset, $len, $depth, &$items ) {
278 $len = (int) $len;
279 if ( $len < 0 || $len > (int) floor( ( self::MAX_ITEMS - $items ) / 2 ) || $len > (int) floor( ( strlen( $bytes ) - $offset ) / 2 ) ) {
280 throw new \Exception( 'Invalid msgpack map length' );
281 }
282 $out = array();
283 for ( $i = 0; $i < $len; $i++ ) {
284 $key = self::read( $bytes, $offset, $depth + 1, $items );
285 if ( ! is_int( $key ) && ! is_string( $key ) ) {
286 throw new \Exception( 'Invalid msgpack map key' );
287 }
288 $out[ $key ] = self::read( $bytes, $offset, $depth + 1, $items );
289 }
290 return $out;
291 }
292
293 /**
294 * Encode native floats in network byte order without PHP 7-only pack codes.
295 */
296 private static function pack_float_be( $value, $format ) {
297 $bytes = pack( $format, $value );
298 return self::is_little_endian() ? strrev( $bytes ) : $bytes;
299 }
300
301 /**
302 * Decode network-byte-order floats with PHP 5.6-compatible pack codes.
303 */
304 private static function unpack_float_be( $bytes, $format ) {
305 if ( self::is_little_endian() ) {
306 $bytes = strrev( $bytes );
307 }
308 $value = unpack( $format, $bytes );
309 if ( false === $value || ! isset( $value[1] ) ) {
310 throw new \Exception( 'Invalid msgpack float' );
311 }
312 return $value[1];
313 }
314
315 private static function is_little_endian() {
316 static $little = null;
317 if ( null === $little ) {
318 $little = "\x01\x00" === pack( 'S', 1 );
319 }
320 return $little;
321 }
322
323 private static function is_list( $array ) {
324 if ( empty( $array ) ) {
325 return false;
326 }
327 return array_keys( $array ) === range( 0, count( $array ) - 1 );
328 }
329 }
330