1<?php 2 3// Protocol Buffers - Google's data interchange format 4// Copyright 2008 Google Inc. All rights reserved. 5// https://developers.google.com/protocol-buffers/ 6// 7// Redistribution and use in source and binary forms, with or without 8// modification, are permitted provided that the following conditions are 9// met: 10// 11// * Redistributions of source code must retain the above copyright 12// notice, this list of conditions and the following disclaimer. 13// * Redistributions in binary form must reproduce the above 14// copyright notice, this list of conditions and the following disclaimer 15// in the documentation and/or other materials provided with the 16// distribution. 17// * Neither the name of Google Inc. nor the names of its 18// contributors may be used to endorse or promote products derived from 19// this software without specific prior written permission. 20// 21// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 33namespace Google\Protobuf\Internal; 34 35use Google\Protobuf\Internal\Uint64; 36 37class CodedInputStream 38{ 39 40 private $buffer; 41 private $buffer_size_after_limit; 42 private $buffer_end; 43 private $current; 44 private $current_limit; 45 private $legitimate_message_end; 46 private $recursion_budget; 47 private $recursion_limit; 48 private $total_bytes_limit; 49 private $total_bytes_read; 50 51 const MAX_VARINT_BYTES = 10; 52 const DEFAULT_RECURSION_LIMIT = 100; 53 const DEFAULT_TOTAL_BYTES_LIMIT = 33554432; // 32 << 20, 32MB 54 55 public function __construct($buffer) 56 { 57 $start = 0; 58 $end = strlen($buffer); 59 $this->buffer = $buffer; 60 $this->buffer_size_after_limit = 0; 61 $this->buffer_end = $end; 62 $this->current = $start; 63 $this->current_limit = $end; 64 $this->legitimate_message_end = false; 65 $this->recursion_budget = self::DEFAULT_RECURSION_LIMIT; 66 $this->recursion_limit = self::DEFAULT_RECURSION_LIMIT; 67 $this->total_bytes_limit = self::DEFAULT_TOTAL_BYTES_LIMIT; 68 $this->total_bytes_read = $end - $start; 69 } 70 71 private function advance($amount) 72 { 73 $this->current += $amount; 74 } 75 76 public function bufferSize() 77 { 78 return $this->buffer_end - $this->current; 79 } 80 81 public function current() 82 { 83 return $this->total_bytes_read - 84 ($this->buffer_end - $this->current + 85 $this->buffer_size_after_limit); 86 } 87 88 public function substr($start, $end) 89 { 90 return substr($this->buffer, $start, $end - $start); 91 } 92 93 private function recomputeBufferLimits() 94 { 95 $this->buffer_end += $this->buffer_size_after_limit; 96 $closest_limit = min($this->current_limit, $this->total_bytes_limit); 97 if ($closest_limit < $this->total_bytes_read) { 98 // The limit position is in the current buffer. We must adjust the 99 // buffer size accordingly. 100 $this->buffer_size_after_limit = $this->total_bytes_read - 101 $closest_limit; 102 $this->buffer_end -= $this->buffer_size_after_limit; 103 } else { 104 $this->buffer_size_after_limit = 0; 105 } 106 } 107 108 private function consumedEntireMessage() 109 { 110 return $this->legitimate_message_end; 111 } 112 113 /** 114 * Read uint32 into $var. Advance buffer with consumed bytes. If the 115 * contained varint is larger than 32 bits, discard the high order bits. 116 * @param $var. 117 */ 118 public function readVarint32(&$var) 119 { 120 if (!$this->readVarint64($var)) { 121 return false; 122 } 123 124 if (PHP_INT_SIZE == 4) { 125 $var = bcmod($var, 4294967296); 126 } else { 127 $var &= 0xFFFFFFFF; 128 } 129 130 // Convert large uint32 to int32. 131 if ($var > 0x7FFFFFFF) { 132 if (PHP_INT_SIZE === 8) { 133 $var = $var | (0xFFFFFFFF << 32); 134 } else { 135 $var = bcsub($var, 4294967296); 136 } 137 } 138 139 $var = intval($var); 140 return true; 141 } 142 143 /** 144 * Read Uint64 into $var. Advance buffer with consumed bytes. 145 * @param $var. 146 */ 147 public function readVarint64(&$var) 148 { 149 $count = 0; 150 151 if (PHP_INT_SIZE == 4) { 152 $high = 0; 153 $low = 0; 154 $b = 0; 155 156 do { 157 if ($this->current === $this->buffer_end) { 158 return false; 159 } 160 if ($count === self::MAX_VARINT_BYTES) { 161 return false; 162 } 163 $b = ord($this->buffer[$this->current]); 164 $bits = 7 * $count; 165 if ($bits >= 32) { 166 $high |= (($b & 0x7F) << ($bits - 32)); 167 } else if ($bits > 25){ 168 // $bits is 28 in this case. 169 $low |= (($b & 0x7F) << 28); 170 $high = ($b & 0x7F) >> 4; 171 } else { 172 $low |= (($b & 0x7F) << $bits); 173 } 174 175 $this->advance(1); 176 $count += 1; 177 } while ($b & 0x80); 178 179 $var = GPBUtil::combineInt32ToInt64($high, $low); 180 if (bccomp($var, 0) < 0) { 181 $var = bcadd($var, "18446744073709551616"); 182 } 183 } else { 184 $result = 0; 185 $shift = 0; 186 187 do { 188 if ($this->current === $this->buffer_end) { 189 return false; 190 } 191 if ($count === self::MAX_VARINT_BYTES) { 192 return false; 193 } 194 195 $byte = ord($this->buffer[$this->current]); 196 $result |= ($byte & 0x7f) << $shift; 197 $shift += 7; 198 $this->advance(1); 199 $count += 1; 200 } while ($byte > 0x7f); 201 202 $var = $result; 203 } 204 205 return true; 206 } 207 208 /** 209 * Read int into $var. If the result is larger than the largest integer, $var 210 * will be -1. Advance buffer with consumed bytes. 211 * @param $var. 212 */ 213 public function readVarintSizeAsInt(&$var) 214 { 215 if (!$this->readVarint64($var)) { 216 return false; 217 } 218 $var = (int)$var; 219 return true; 220 } 221 222 /** 223 * Read 32-bit unsigned integer to $var. If the buffer has less than 4 bytes, 224 * return false. Advance buffer with consumed bytes. 225 * @param $var. 226 */ 227 public function readLittleEndian32(&$var) 228 { 229 $data = null; 230 if (!$this->readRaw(4, $data)) { 231 return false; 232 } 233 $var = unpack('V', $data); 234 $var = $var[1]; 235 return true; 236 } 237 238 /** 239 * Read 64-bit unsigned integer to $var. If the buffer has less than 8 bytes, 240 * return false. Advance buffer with consumed bytes. 241 * @param $var. 242 */ 243 public function readLittleEndian64(&$var) 244 { 245 $data = null; 246 if (!$this->readRaw(4, $data)) { 247 return false; 248 } 249 $low = unpack('V', $data)[1]; 250 if (!$this->readRaw(4, $data)) { 251 return false; 252 } 253 $high = unpack('V', $data)[1]; 254 if (PHP_INT_SIZE == 4) { 255 $var = GPBUtil::combineInt32ToInt64($high, $low); 256 } else { 257 $var = ($high << 32) | $low; 258 } 259 return true; 260 } 261 262 /** 263 * Read tag into $var. Advance buffer with consumed bytes. 264 * @param $var. 265 */ 266 public function readTag() 267 { 268 if ($this->current === $this->buffer_end) { 269 // Make sure that it failed due to EOF, not because we hit 270 // total_bytes_limit, which, unlike normal limits, is not a valid 271 // place to end a message. 272 $current_position = $this->total_bytes_read - 273 $this->buffer_size_after_limit; 274 if ($current_position >= $this->total_bytes_limit) { 275 // Hit total_bytes_limit_. But if we also hit the normal limit, 276 // we're still OK. 277 $this->legitimate_message_end = 278 ($this->current_limit === $this->total_bytes_limit); 279 } else { 280 $this->legitimate_message_end = true; 281 } 282 return 0; 283 } 284 285 $result = 0; 286 // The largest tag is 2^29 - 1, which can be represented by int32. 287 $success = $this->readVarint32($result); 288 if ($success) { 289 return $result; 290 } else { 291 return 0; 292 } 293 } 294 295 public function readRaw($size, &$buffer) 296 { 297 $current_buffer_size = 0; 298 if ($this->bufferSize() < $size) { 299 return false; 300 } 301 302 if ($size === 0) { 303 $buffer = ""; 304 } else { 305 $buffer = substr($this->buffer, $this->current, $size); 306 $this->advance($size); 307 } 308 309 return true; 310 } 311 312 /* Places a limit on the number of bytes that the stream may read, starting 313 * from the current position. Once the stream hits this limit, it will act 314 * like the end of the input has been reached until popLimit() is called. 315 * 316 * As the names imply, the stream conceptually has a stack of limits. The 317 * shortest limit on the stack is always enforced, even if it is not the top 318 * limit. 319 * 320 * The value returned by pushLimit() is opaque to the caller, and must be 321 * passed unchanged to the corresponding call to popLimit(). 322 * 323 * @param integer $byte_limit 324 * @throws \Exception Fail to push limit. 325 */ 326 public function pushLimit($byte_limit) 327 { 328 // Current position relative to the beginning of the stream. 329 $current_position = $this->current(); 330 $old_limit = $this->current_limit; 331 332 // security: byte_limit is possibly evil, so check for negative values 333 // and overflow. 334 if ($byte_limit >= 0 && 335 $byte_limit <= PHP_INT_MAX - $current_position && 336 $byte_limit <= $this->current_limit - $current_position) { 337 $this->current_limit = $current_position + $byte_limit; 338 $this->recomputeBufferLimits(); 339 } else { 340 throw new GPBDecodeException("Fail to push limit."); 341 } 342 343 return $old_limit; 344 } 345 346 /* The limit passed in is actually the *old* limit, which we returned from 347 * PushLimit(). 348 * 349 * @param integer $byte_limit 350 */ 351 public function popLimit($byte_limit) 352 { 353 $this->current_limit = $byte_limit; 354 $this->recomputeBufferLimits(); 355 // We may no longer be at a legitimate message end. ReadTag() needs to 356 // be called again to find out. 357 $this->legitimate_message_end = false; 358 } 359 360 public function incrementRecursionDepthAndPushLimit( 361 $byte_limit, &$old_limit, &$recursion_budget) 362 { 363 $old_limit = $this->pushLimit($byte_limit); 364 $recursion_limit = --$this->recursion_limit; 365 } 366 367 public function decrementRecursionDepthAndPopLimit($byte_limit) 368 { 369 $result = $this->consumedEntireMessage(); 370 $this->popLimit($byte_limit); 371 ++$this->recursion_budget; 372 return $result; 373 } 374 375 public function bytesUntilLimit() 376 { 377 if ($this->current_limit === PHP_INT_MAX) { 378 return -1; 379 } 380 return $this->current_limit - $this->current; 381 } 382} 383