blob: 7ec3ca2294b82c9a676006e9eaca9c8e59d2aaec [file] [log] [blame]
avm9996399bb77c2020-01-27 03:15:08 +01001<?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\Duration;
36use Google\Protobuf\FieldMask;
37use Google\Protobuf\Internal\GPBType;
38use Google\Protobuf\Internal\RepeatedField;
39use Google\Protobuf\Internal\MapField;
40
41function camel2underscore($input) {
42 preg_match_all(
43 '!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!',
44 $input,
45 $matches);
46 $ret = $matches[0];
47 foreach ($ret as &$match) {
48 $match = $match == strtoupper($match) ? strtolower($match) : lcfirst($match);
49 }
50 return implode('_', $ret);
51}
52
53class GPBUtil
54{
55 const NANOS_PER_MILLISECOND = 1000000;
56 const NANOS_PER_MICROSECOND = 1000;
57 const TYPE_URL_PREFIX = 'type.googleapis.com/';
58
59 public static function divideInt64ToInt32($value, &$high, &$low, $trim = false)
60 {
61 $isNeg = (bccomp($value, 0) < 0);
62 if ($isNeg) {
63 $value = bcsub(0, $value);
64 }
65
66 $high = bcdiv($value, 4294967296);
67 $low = bcmod($value, 4294967296);
68 if (bccomp($high, 2147483647) > 0) {
69 $high = (int) bcsub($high, 4294967296);
70 } else {
71 $high = (int) $high;
72 }
73 if (bccomp($low, 2147483647) > 0) {
74 $low = (int) bcsub($low, 4294967296);
75 } else {
76 $low = (int) $low;
77 }
78
79 if ($isNeg) {
80 $high = ~$high;
81 $low = ~$low;
82 $low++;
83 if (!$low) {
84 $high = (int)($high + 1);
85 }
86 }
87
88 if ($trim) {
89 $high = 0;
90 }
91 }
92
93 public static function checkString(&$var, $check_utf8)
94 {
95 if (is_array($var) || is_object($var)) {
96 throw new \InvalidArgumentException("Expect string.");
97 }
98 if (!is_string($var)) {
99 $var = strval($var);
100 }
101 if ($check_utf8 && !preg_match('//u', $var)) {
102 throw new \Exception("Expect utf-8 encoding.");
103 }
104 }
105
106 public static function checkEnum(&$var)
107 {
108 static::checkInt32($var);
109 }
110
111 public static function checkInt32(&$var)
112 {
113 if (is_numeric($var)) {
114 $var = intval($var);
115 } else {
116 throw new \Exception("Expect integer.");
117 }
118 }
119
120 public static function checkUint32(&$var)
121 {
122 if (is_numeric($var)) {
123 if (PHP_INT_SIZE === 8) {
124 $var = intval($var);
125 $var |= ((-(($var >> 31) & 0x1)) & ~0xFFFFFFFF);
126 } else {
127 if (bccomp($var, 0x7FFFFFFF) > 0) {
128 $var = bcsub($var, "4294967296");
129 }
130 $var = (int) $var;
131 }
132 } else {
133 throw new \Exception("Expect integer.");
134 }
135 }
136
137 public static function checkInt64(&$var)
138 {
139 if (is_numeric($var)) {
140 if (PHP_INT_SIZE == 8) {
141 $var = intval($var);
142 } else {
143 if (is_float($var) ||
144 is_integer($var) ||
145 (is_string($var) &&
146 bccomp($var, "9223372036854774784") < 0)) {
147 $var = number_format($var, 0, ".", "");
148 }
149 }
150 } else {
151 throw new \Exception("Expect integer.");
152 }
153 }
154
155 public static function checkUint64(&$var)
156 {
157 if (is_numeric($var)) {
158 if (PHP_INT_SIZE == 8) {
159 $var = intval($var);
160 } else {
161 $var = number_format($var, 0, ".", "");
162 }
163 } else {
164 throw new \Exception("Expect integer.");
165 }
166 }
167
168 public static function checkFloat(&$var)
169 {
170 if (is_float($var) || is_numeric($var)) {
171 $var = floatval($var);
172 } else {
173 throw new \Exception("Expect float.");
174 }
175 }
176
177 public static function checkDouble(&$var)
178 {
179 if (is_float($var) || is_numeric($var)) {
180 $var = floatval($var);
181 } else {
182 throw new \Exception("Expect float.");
183 }
184 }
185
186 public static function checkBool(&$var)
187 {
188 if (is_array($var) || is_object($var)) {
189 throw new \Exception("Expect boolean.");
190 }
191 $var = boolval($var);
192 }
193
194 public static function checkMessage(&$var, $klass, $newClass = null)
195 {
196 if (!$var instanceof $klass && !is_null($var)) {
197 throw new \Exception("Expect $klass.");
198 }
199 }
200
201 public static function checkRepeatedField(&$var, $type, $klass = null)
202 {
203 if (!$var instanceof RepeatedField && !is_array($var)) {
204 throw new \Exception("Expect array.");
205 }
206 if (is_array($var)) {
207 $tmp = new RepeatedField($type, $klass);
208 foreach ($var as $value) {
209 $tmp[] = $value;
210 }
211 return $tmp;
212 } else {
213 if ($var->getType() != $type) {
214 throw new \Exception(
215 "Expect repeated field of different type.");
216 }
217 if ($var->getType() === GPBType::MESSAGE &&
218 $var->getClass() !== $klass &&
219 $var->getLegacyClass() !== $klass) {
220 throw new \Exception(
221 "Expect repeated field of " . $klass . ".");
222 }
223 return $var;
224 }
225 }
226
227 public static function checkMapField(&$var, $key_type, $value_type, $klass = null)
228 {
229 if (!$var instanceof MapField && !is_array($var)) {
230 throw new \Exception("Expect dict.");
231 }
232 if (is_array($var)) {
233 $tmp = new MapField($key_type, $value_type, $klass);
234 foreach ($var as $key => $value) {
235 $tmp[$key] = $value;
236 }
237 return $tmp;
238 } else {
239 if ($var->getKeyType() != $key_type) {
240 throw new \Exception("Expect map field of key type.");
241 }
242 if ($var->getValueType() != $value_type) {
243 throw new \Exception("Expect map field of value type.");
244 }
245 if ($var->getValueType() === GPBType::MESSAGE &&
246 $var->getValueClass() !== $klass &&
247 $var->getLegacyValueClass() !== $klass) {
248 throw new \Exception(
249 "Expect map field of " . $klass . ".");
250 }
251 return $var;
252 }
253 }
254
255 public static function Int64($value)
256 {
257 return new Int64($value);
258 }
259
260 public static function Uint64($value)
261 {
262 return new Uint64($value);
263 }
264
265 public static function getClassNamePrefix(
266 $classname,
267 $file_proto)
268 {
269 $option = $file_proto->getOptions();
270 $prefix = is_null($option) ? "" : $option->getPhpClassPrefix();
271 if ($prefix !== "") {
272 return $prefix;
273 }
274
275 $reserved_words = array(
276 "abstract"=>0, "and"=>0, "array"=>0, "as"=>0, "break"=>0,
277 "callable"=>0, "case"=>0, "catch"=>0, "class"=>0, "clone"=>0,
278 "const"=>0, "continue"=>0, "declare"=>0, "default"=>0, "die"=>0,
279 "do"=>0, "echo"=>0, "else"=>0, "elseif"=>0, "empty"=>0,
280 "enddeclare"=>0, "endfor"=>0, "endforeach"=>0, "endif"=>0,
281 "endswitch"=>0, "endwhile"=>0, "eval"=>0, "exit"=>0, "extends"=>0,
282 "final"=>0, "for"=>0, "foreach"=>0, "function"=>0, "global"=>0,
283 "goto"=>0, "if"=>0, "implements"=>0, "include"=>0,
284 "include_once"=>0, "instanceof"=>0, "insteadof"=>0, "interface"=>0,
285 "isset"=>0, "list"=>0, "namespace"=>0, "new"=>0, "or"=>0,
286 "print"=>0, "private"=>0, "protected"=>0, "public"=>0, "require"=>0,
287 "require_once"=>0, "return"=>0, "static"=>0, "switch"=>0,
288 "throw"=>0, "trait"=>0, "try"=>0, "unset"=>0, "use"=>0, "var"=>0,
289 "while"=>0, "xor"=>0, "int"=>0, "float"=>0, "bool"=>0, "string"=>0,
290 "true"=>0, "false"=>0, "null"=>0, "void"=>0, "iterable"=>0
291 );
292
293 if (array_key_exists(strtolower($classname), $reserved_words)) {
294 if ($file_proto->getPackage() === "google.protobuf") {
295 return "GPB";
296 } else {
297 return "PB";
298 }
299 }
300
301 return "";
302 }
303
304 public static function getLegacyClassNameWithoutPackage(
305 $name,
306 $file_proto)
307 {
308 $classname = implode('_', explode('.', $name));
309 return static::getClassNamePrefix($classname, $file_proto) . $classname;
310 }
311
312 public static function getClassNameWithoutPackage(
313 $name,
314 $file_proto)
315 {
316 $parts = explode('.', $name);
317 foreach ($parts as $i => $part) {
318 $parts[$i] = static::getClassNamePrefix($parts[$i], $file_proto) . $parts[$i];
319 }
320 return implode('\\', $parts);
321 }
322
323 public static function getFullClassName(
324 $proto,
325 $containing,
326 $file_proto,
327 &$message_name_without_package,
328 &$classname,
329 &$legacy_classname,
330 &$fullname)
331 {
332 // Full name needs to start with '.'.
333 $message_name_without_package = $proto->getName();
334 if ($containing !== "") {
335 $message_name_without_package =
336 $containing . "." . $message_name_without_package;
337 }
338
339 $package = $file_proto->getPackage();
340 if ($package === "") {
341 $fullname = "." . $message_name_without_package;
342 } else {
343 $fullname = "." . $package . "." . $message_name_without_package;
344 }
345
346 $class_name_without_package =
347 static::getClassNameWithoutPackage($message_name_without_package, $file_proto);
348 $legacy_class_name_without_package =
349 static::getLegacyClassNameWithoutPackage(
350 $message_name_without_package, $file_proto);
351
352 $option = $file_proto->getOptions();
353 if (!is_null($option) && $option->hasPhpNamespace()) {
354 $namespace = $option->getPhpNamespace();
355 if ($namespace !== "") {
356 $classname = $namespace . "\\" . $class_name_without_package;
357 $legacy_classname =
358 $namespace . "\\" . $legacy_class_name_without_package;
359 return;
360 } else {
361 $classname = $class_name_without_package;
362 $legacy_classname = $legacy_class_name_without_package;
363 return;
364 }
365 }
366
367 if ($package === "") {
368 $classname = $class_name_without_package;
369 $legacy_classname = $legacy_class_name_without_package;
370 } else {
371 $parts = array_map('ucwords', explode('.', $package));
372 foreach ($parts as $i => $part) {
373 $parts[$i] = self::getClassNamePrefix($part, $file_proto).$part;
374 }
375 $classname =
376 implode('\\', $parts) .
377 "\\".self::getClassNamePrefix($class_name_without_package,$file_proto).
378 $class_name_without_package;
379 $legacy_classname =
380 implode('\\', array_map('ucwords', explode('.', $package))).
381 "\\".$legacy_class_name_without_package;
382 }
383 }
384
385 public static function combineInt32ToInt64($high, $low)
386 {
387 $isNeg = $high < 0;
388 if ($isNeg) {
389 $high = ~$high;
390 $low = ~$low;
391 $low++;
392 if (!$low) {
393 $high = (int) ($high + 1);
394 }
395 }
396 $result = bcadd(bcmul($high, 4294967296), $low);
397 if ($low < 0) {
398 $result = bcadd($result, 4294967296);
399 }
400 if ($isNeg) {
401 $result = bcsub(0, $result);
402 }
403 return $result;
404 }
405
406 public static function parseTimestamp($timestamp)
407 {
408 // prevent parsing timestamps containing with the non-existant year "0000"
409 // DateTime::createFromFormat parses without failing but as a nonsensical date
410 if (substr($timestamp, 0, 4) === "0000") {
411 throw new \Exception("Year cannot be zero.");
412 }
413 // prevent parsing timestamps ending with a lowercase z
414 if (substr($timestamp, -1, 1) === "z") {
415 throw new \Exception("Timezone cannot be a lowercase z.");
416 }
417
418 $nanoseconds = 0;
419 $periodIndex = strpos($timestamp, ".");
420 if ($periodIndex !== false) {
421 $nanosecondsLength = 0;
422 // find the next non-numeric character in the timestamp to calculate
423 // the length of the nanoseconds text
424 for ($i = $periodIndex + 1, $length = strlen($timestamp); $i < $length; $i++) {
425 if (!is_numeric($timestamp[$i])) {
426 $nanosecondsLength = $i - ($periodIndex + 1);
427 break;
428 }
429 }
430 if ($nanosecondsLength % 3 !== 0) {
431 throw new \Exception("Nanoseconds must be disible by 3.");
432 }
433 if ($nanosecondsLength > 9) {
434 throw new \Exception("Nanoseconds must be in the range of 0 to 999,999,999 nanoseconds.");
435 }
436 if ($nanosecondsLength > 0) {
437 $nanoseconds = substr($timestamp, $periodIndex + 1, $nanosecondsLength);
438 $nanoseconds = intval($nanoseconds);
439
440 // remove the nanoseconds and preceding period from the timestamp
441 $date = substr($timestamp, 0, $periodIndex);
442 $timezone = substr($timestamp, $periodIndex + $nanosecondsLength + 1);
443 $timestamp = $date.$timezone;
444 }
445 }
446
447 $date = \DateTime::createFromFormat(\DateTime::RFC3339, $timestamp, new \DateTimeZone("UTC"));
448 if ($date === false) {
449 throw new \Exception("Invalid RFC 3339 timestamp.");
450 }
451
452 $value = new \Google\Protobuf\Timestamp();
453 $seconds = $date->format("U");
454 $value->setSeconds($seconds);
455 $value->setNanos($nanoseconds);
456 return $value;
457 }
458
459 public static function formatTimestamp($value)
460 {
461 if (bccomp($value->getSeconds(), "253402300800") != -1) {
462 throw new GPBDecodeException("Duration number too large.");
463 }
464 if (bccomp($value->getSeconds(), "-62135596801") != 1) {
465 throw new GPBDecodeException("Duration number too small.");
466 }
467 $nanoseconds = static::getNanosecondsForTimestamp($value->getNanos());
468 if (!empty($nanoseconds)) {
469 $nanoseconds = ".".$nanoseconds;
470 }
471 $date = new \DateTime('@'.$value->getSeconds(), new \DateTimeZone("UTC"));
472 return $date->format("Y-m-d\TH:i:s".$nanoseconds."\Z");
473 }
474
475 public static function parseDuration($value)
476 {
477 if (strlen($value) < 2 || substr($value, -1) !== "s") {
478 throw new GPBDecodeException("Missing s after duration string");
479 }
480 $number = substr($value, 0, -1);
481 if (bccomp($number, "315576000001") != -1) {
482 throw new GPBDecodeException("Duration number too large.");
483 }
484 if (bccomp($number, "-315576000001") != 1) {
485 throw new GPBDecodeException("Duration number too small.");
486 }
487 $pos = strrpos($number, ".");
488 if ($pos !== false) {
489 $seconds = substr($number, 0, $pos);
490 if (bccomp($seconds, 0) < 0) {
491 $nanos = bcmul("0" . substr($number, $pos), -1000000000);
492 } else {
493 $nanos = bcmul("0" . substr($number, $pos), 1000000000);
494 }
495 } else {
496 $seconds = $number;
497 $nanos = 0;
498 }
499 $duration = new Duration();
500 $duration->setSeconds($seconds);
501 $duration->setNanos($nanos);
502 return $duration;
503 }
504
505 public static function formatDuration($value)
506 {
507 if (bccomp($value->getSeconds(), '315576000001') != -1) {
508 throw new GPBDecodeException('Duration number too large.');
509 }
510 if (bccomp($value->getSeconds(), '-315576000001') != 1) {
511 throw new GPBDecodeException('Duration number too small.');
512 }
513
514 $nanos = $value->getNanos();
515 if ($nanos === 0) {
516 return (string) $value->getSeconds();
517 }
518
519 if ($nanos % 1000000 === 0) {
520 $digits = 3;
521 } elseif ($nanos % 1000 === 0) {
522 $digits = 6;
523 } else {
524 $digits = 9;
525 }
526
527 $nanos = bcdiv($nanos, '1000000000', $digits);
528 return bcadd($value->getSeconds(), $nanos, $digits);
529 }
530
531 public static function parseFieldMask($paths_string)
532 {
533 $field_mask = new FieldMask();
534 if (strlen($paths_string) === 0) {
535 return $field_mask;
536 }
537 $path_strings = explode(",", $paths_string);
538 $paths = $field_mask->getPaths();
539 foreach($path_strings as &$path_string) {
540 $field_strings = explode(".", $path_string);
541 foreach($field_strings as &$field_string) {
542 $field_string = camel2underscore($field_string);
543 }
544 $path_string = implode(".", $field_strings);
545 $paths[] = $path_string;
546 }
547 return $field_mask;
548 }
549
550 public static function formatFieldMask($field_mask)
551 {
552 $converted_paths = [];
553 foreach($field_mask->getPaths() as $path) {
554 $fields = explode('.', $path);
555 $converted_path = [];
556 foreach ($fields as $field) {
557 $segments = explode('_', $field);
558 $start = true;
559 $converted_segments = "";
560 foreach($segments as $segment) {
561 if (!$start) {
562 $converted = ucfirst($segment);
563 } else {
564 $converted = $segment;
565 $start = false;
566 }
567 $converted_segments .= $converted;
568 }
569 $converted_path []= $converted_segments;
570 }
571 $converted_path = implode(".", $converted_path);
572 $converted_paths []= $converted_path;
573 }
574 return implode(",", $converted_paths);
575 }
576
577 public static function getNanosecondsForTimestamp($nanoseconds)
578 {
579 if ($nanoseconds == 0) {
580 return '';
581 }
582 if ($nanoseconds % static::NANOS_PER_MILLISECOND == 0) {
583 return sprintf('%03d', $nanoseconds / static::NANOS_PER_MILLISECOND);
584 }
585 if ($nanoseconds % static::NANOS_PER_MICROSECOND == 0) {
586 return sprintf('%06d', $nanoseconds / static::NANOS_PER_MICROSECOND);
587 }
588 return sprintf('%09d', $nanoseconds);
589 }
590
591 public static function hasSpecialJsonMapping($msg)
592 {
593 return is_a($msg, 'Google\Protobuf\Any') ||
594 is_a($msg, "Google\Protobuf\ListValue") ||
595 is_a($msg, "Google\Protobuf\Struct") ||
596 is_a($msg, "Google\Protobuf\Value") ||
597 is_a($msg, "Google\Protobuf\Duration") ||
598 is_a($msg, "Google\Protobuf\Timestamp") ||
599 is_a($msg, "Google\Protobuf\FieldMask") ||
600 static::hasJsonValue($msg);
601 }
602
603 public static function hasJsonValue($msg)
604 {
605 return is_a($msg, "Google\Protobuf\DoubleValue") ||
606 is_a($msg, "Google\Protobuf\FloatValue") ||
607 is_a($msg, "Google\Protobuf\Int64Value") ||
608 is_a($msg, "Google\Protobuf\UInt64Value") ||
609 is_a($msg, "Google\Protobuf\Int32Value") ||
610 is_a($msg, "Google\Protobuf\UInt32Value") ||
611 is_a($msg, "Google\Protobuf\BoolValue") ||
612 is_a($msg, "Google\Protobuf\StringValue") ||
613 is_a($msg, "Google\Protobuf\BytesValue");
614 }
615}